这是每一个Java程序员都知道的程序。 我想看看我们能从这个简单的程序。 一个简单的开始可能导致学习更复杂的东西变得更加容易。 这将是伟大的如果这篇文章读起来很有趣,不仅为Java程序员入门级。 请留下你的评论,如果hello world意味着更多的给你。
HelloWorld.java
public class HelloWorld {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("Hello World");
}
} |
1。 为什么一切都开始于一个类?
Java程序是由类,每一个方法和字段在一个类。 这是由于其面向对象的特性。 面向对象的编程语言有很多的优点,如模块化、可扩展性等。
2。 然后“主要”方法——程序入口
“主要”方法是程序入口,它是静态的。 “静态”意味着该方法类的一部分,不是对象的一部分。
那是为什么? 我们为什么不把一个非静态方法是程序入口?
如果一个方法不是静态的,那么需要首先创建一个对象使用方法。 因为必须调用一个对象的方法。 对于一个入口,这是不现实的。 因此,程序入口方法是静态的。
参数“String[]参数”表示一个字符串数组可以发送程序帮助程序初始化。
3。 字节码的HelloWorld
执行程序,Java文件第一次编译为Java字节代码存储在。 类文件。
字节代码看起来像什么?
字节代码本身是不可读的。 如果我们使用十六进制编辑器,它看起来像下面的:
我们可以看到很多操作码(如。 CA,4 c等)在上面的字节码中,他们每个人都有一个相应的助记码(如。 在下面的例子中,aload_0)。 操作码是不可读的,但我们可以使用javap的记忆形式。 类文件。
“javap - c”打印出每个方法在类反汇编代码。 反汇编代码意味着Java字节码指令组成。
javap -classpath。 - c HelloWorld
Compiled from "HelloWorld.java"
public class HelloWorld extends java.lang.Object{
public HelloWorld();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3; //String Hello World
5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
|
上面的代码包含两个方法:一个是默认的构造函数,编译器推断的;另一种是主要方法。
下面每个方法,有一系列的指令,如aload_0 invokespecial # 1等。什么每条指令可以抬起头来 Java字节码指令列表 。 例如,aload_0加载一个引用从局部变量0压入堆栈,getstatic获取类的一个静态字段值。 注意到“2”getstatic指令指向运行时常量池。 常量池的 JVM运行时数据区 。 这让我们看看常量池,这可以通过使用“javap - verbose”命令。
此外,每条指令开始于一个数字,如0、1,4,等等。。 类文件,每个方法都有一个对应的字节数组。 这些数字对应的索引数组,其中每个操作码及其参数存储。 每个操作码1个字节长,说明可以有0或多个参数。 这就是为什么这些数字并不连续。
现在,我们可以使用“javap - verbose”采取进一步的类。
javap -classpath。 - verbose HelloWorld
Compiled from "HelloWorld.java"
public class HelloWorld extends java.lang.Object{
public HelloWorld();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3; //String Hello World
5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
|
Compiled from "HelloWorld.java"
public class HelloWorld extends java.lang.Object
SourceFile: "HelloWorld.java"
minor version: 0
major version: 50
Constant 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=1
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 2: 0
public static void main(java.lang.String[]);
Code:
Stack=2, Locals=1, Args_size=1
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3; //String Hello World
5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 9: 0
line 10: 8
}
|
从 JVM规范 :运行时常量池是一个函数的符号表类似传统的编程语言,尽管它包含了一个广泛的数据比一个典型的符号表。
“# 1”“invokespecial # 1”指令指向常量池中的# 1常数。 不变的是“方法# 6,# 15;”。 的号码,我们就可以按递归方式来得到最终的常数。
LineNumberTable提供调试器显示这行信息对应于Java源代码的字节码指令。 例如,Java源代码中的第9行对应于0字节代码的主要方法和第10行对应于8字节码。
如果你想知道更多关于字节码,您可以创建和编译一个更复杂的类来看看。 HelloWorld确实是这样做的出发点。
4。 它在JVM中执行的是怎样的?
现在的问题是如何JVM加载类和调用的主要方法?
JVM执行的主要方法之前,需要加载,链接,初始化类。 为一个类/接口带来二进制形式加载到JVM。 链接包含二进制数据类型到JVM运行时的状态。 连接分为3步:验证,准备,和可选的决议。 验证保证了类/接口结构是正确的。 类/接口所需的准备包括分配内存。 分辨率解析符号引用。 最后在初始化步骤中,类变量给出适当的初始值。
这是通过Java类加载器加载工作。 当JVM启动时,三个类加载器使用:
- 引导类加载器加载:位于核心Java库 / jre / lib目录中。 它是核心JVM的一部分,是用原生代码写的。
- 扩展类装入器:加载扩展目录中的代码(例如, / jar / lib / ext)。
- 系统类装入器:加载代码在类路径中找到。
因此HelloWorld类由系统类加载。 当执行的主要方法,它将触发 加载、链接和其他类的初始化 如果他们存在。
最后,main()框架被推到JVM堆栈,并相应地设置程序计数器(PC)。 电脑然后指出推动println()JVM堆栈帧。 main()方法完成后,它将从堆栈中弹出和执行完成
Compiled from "HelloWorld.java"
public class HelloWorld extends java.lang.Object{
public HelloWorld();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3; //String Hello World
5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
|