当前位置: 首页 > ai >正文

学了那么久的java你还不明白HelloWorld的原理吗?速看!

打印HelloWorld每个Java程序员都知道的程序。这个程序很简单,但一个简单的开始可以引入对更复杂概念的深入理解。在这篇文章中,我将探索从这个简单的程序中可以学到什么。如果hello world对你来说更重要,请留下你的评论。

代码:

 public class HelloWorld {/*** @param args*/public static void main(String[] args) {// TODO Auto-generated method stubSystem.out.println("Hello World");}
}

1、为什么一切都是从类开始的?

Java程序是从类构建的,每个方法和字段都必须在一个类中。这是由于其面向对象的特性:一切都是一个对象,是一个类的实例。与函数式编程语言相比,面向对象编程语言具有许多优点,如更好的模块性、可扩展性等。

2、为什么总有一个主方法?

“主要”方法是程序入口,它是静态的。“静态”意味着该方法是其类的一部分,而不是对象的一部分。

为什么会这样?为什么我们不把一个非静态的方法作为程序入口呢?

如果方法不是静态的,则需要先创建一个对象才能使用该方法。因为该方法必须在对象上调用。就入口而言,这是不现实的。没有鸡我们就吃不到鸡蛋。因此,程序入口方法是静态的。

参数“String[]args”表示可以向程序发送字符串数组以帮助程序初始化。

3、HelloWorld字节码

要执行程序,.java文件编译为.class文件,那么字节码是什么样子的?字节码本身不可读。如果使用十六进制编辑器,则如下所示:

我们可以在上面的字节码中看到很多操作码(例如CA、4C等),每个操作码都有相应的助记码(例如下面的示例中的 aload_0)。操作码不可读,但我们可以使用javap查看的助记符形式.class文件。

“javap-c”为类中的每个方法打印反汇编代码。反汇编代码是指组成Java字节码的指令。

执行反编译操作:

javap -classpath .  -c HelloWorld

输出结果:

Compiled from "HelloWorld.java"
public class HelloWorld extends java.lang.Object{
public HelloWorld();Code:0:	aload_01:	invokespecial	#1; //Method java/lang/Object."<init>":()V4:	returnpublic static void main(java.lang.String[]);Code:0:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;3:	ldc	#3; //String Hello World5:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V8:	return
}

上面的代码包含两个方法:一个是默认构造函数,由编译器推断;另一种是主要方法。

每个方法下面都有一系列指令,如aload_0、invokespecial#1等。可以在Java字节码指令列表中查找每个指令的作用。例如,aload_0从局部变量0将引用加载到堆栈上,getstatic获取类的静态字段值。请注意getstatic之后的“#2”指令指向运行时常量池。常量池是JVM运行时数据区域之一。这让我们来看看常量池,它可以通过使用“javap-verbose”命令来完成。

此外,每个指令都以数字开头,例如中的0、1、4等。类文件中,每个方法都有相应的字节码数组。这些数字对应于存储每个操作码及其参数的数组的索引。每个操作码的长度为1字节,指令可以有0个或多个参数。这就是为什么这些数字不是连续的原因。

现在,我们可以使用“javap-verbose”进一步了解该类。

执行操作:

javap -classpath . -verbose HelloWorld

输出结果:

Compiled from "HelloWorld.java"
public class HelloWorld extends java.lang.ObjectSourceFile: "HelloWorld.java"minor version: 0major version: 50Constant pool:
const #1 = Method	#6.#15;	//  java/lang/Object."<init>":()V
const #2 = Field	#16.#17;	//  java/lang/System.out:Ljava/io/PrintStream;
const #3 = String	#18;	//  Hello World
const #4 = Method	#19.#20;	//  java/io/PrintStream.println:(Ljava/lang/String;)V
const #5 = class	#21;	//  HelloWorld
const #6 = class	#22;	//  java/lang/Object
const #7 = Asciz	<init>;
const #8 = Asciz	()V;
const #9 = Asciz	Code;
const #10 = Asciz	LineNumberTable;
const #11 = Asciz	main;
const #12 = Asciz	([Ljava/lang/String;)V;
const #13 = Asciz	SourceFile;
const #14 = Asciz	HelloWorld.java;
const #15 = NameAndType	#7:#8;//  "<init>":()V
const #16 = class	#23;	//  java/lang/System
const #17 = NameAndType	#24:#25;//  out:Ljava/io/PrintStream;
const #18 = Asciz	Hello World;
const #19 = class	#26;	//  java/io/PrintStream
const #20 = NameAndType	#27:#28;//  println:(Ljava/lang/String;)V
const #21 = Asciz	HelloWorld;
const #22 = Asciz	java/lang/Object;
const #23 = Asciz	java/lang/System;
const #24 = Asciz	out;
const #25 = Asciz	Ljava/io/PrintStream;;
const #26 = Asciz	java/io/PrintStream;
const #27 = Asciz	println;
const #28 = Asciz	(Ljava/lang/String;)V;{
public HelloWorld();Code:Stack=1, Locals=1, Args_size=10:	aload_01:	invokespecial	#1; //Method java/lang/Object."<init>":()V4:	returnLineNumberTable: line 2: 0public static void main(java.lang.String[]);Code:Stack=2, Locals=1, Args_size=10:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;3:	ldc	#3; //String Hello World5:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V8:	returnLineNumberTable: line 9: 0line 10: 8
}

来自JVM规范:运行时常量池的功能类似于传统编程语言的符号表,尽管它包含比典型符号表更广泛的数据范围。

“invokespecial#1”指令中的“#1”指向常量池中的#1常量,该常数为“Method  #6.#15;”,从这个数字,我们可以递归地得到最终常数。

LineNumberTable向调试器提供信息,以指示哪行Java源代码对应于哪条字节码指令。例如,Java源代码中的第9行对应于main方法中的字节码0,第10行对应于字节码8。

如果您想更多地了解字节码,可以创建并编译一个更复杂的类来查看。HelloWorld实际上是这样做的起点。

4、HelloWorld如何在JVM中执行?

现在的问题是JVM如何加载类并调用main方法?

在执行main方法之前,JVM需要:(1)加载、(2)链接和(3)初始化类。(1) 加载将类/接口的二进制形式引入JVM。(2) 链接将二进制类型的数据合并到JVM的运行时状态中。链接包括3个步骤:验证、准备和可选解决方案。验证确保类/接口的结构正确;准备包括分配类/接口所需的内存;可选解决方案包括解析符号引用。最后,(3)初始化为类变量分配适当的初始值。

 这个加载工作由Java类加载器完成。启动JVM时,会使用三个类加载器:

  1. 引导类加载器:加载位于/jre/lib目录中的核心Java库。它是核心JVM的一部分,用本地代码编写。
  2. 扩展类加载器:加载扩展目录中的代码(例如,/jar/lib/ext)。
  3. 系统类加载器:加载在类路径上找到的代码。

所以HelloWorld类由系统类加载器加载。当执行main方法时,它将触发其他依赖类(如果存在)的加载、链接和初始化。

最后,main()框架被推入JVM堆栈,并相应地设置程序计数器(PC)。PC然后指示将println()框架推送到JVM堆栈。当main()方法完成时,它将从堆栈中弹出并执行完毕。

http://www.xdnf.cn/news/11267.html

相关文章:

  • 强!推荐一款自动化神器Autolt:不再重复工作
  • linux基础(超级详细小白入门篇)
  • 2024年最新source insight教程:常用设置、快捷键、附带source insight3,Golang面试知识点
  • fseek 、fwrite 、fread
  • 检测UDP端口是否畅通方法
  • IOU、GIOU、DIOU、CIOU的学习及代码实现
  • 灰度图的理解(主要在作图像的方面遇到了问题)
  • 如何选择美国高速VPS?实用选购指南
  • SPL - 写着简单跑得又快的数据库语言
  • 数据结构与算法入门
  • node.js详细安装教程
  • 常见中文乱码问题
  • DB2备份和还原数据库+代码详解
  • HTTP/HTTPS(超细精讲)
  • 彻底理解面向对象,看完这一篇就够了
  • 谈谈Parser --王垠
  • 拓扑排序(Topological Sorting)
  • Wireshark TS | Ping Google DNS 8.8.8.8 特殊结果解析
  • centos 7 RAID0磁盘阵列步骤_如何重做raid但是不格式化磁盘
  • strtok函数
  • FastReport Avalonia 2024
  • 初识 ABP 框架
  • FFMPEG开发手册
  • Python 十五个炫酷代码
  • 开源 SPL 消灭数以万计的数据库中间表
  • 轻松两招,告诉你电脑怎么定时关机!
  • 央视解说之韩乔生巅峰之作--夏普
  • 我有七种实现web实时消息推送的方案
  • 多普达与O2之间的关系
  • 【编译原理】-- 第一章(翻译程序、编译程序、汇编程序、解释程序、编译过程概述)