从HelloWorld中我们能学到什么

来源:互联网 发布:mac safari开启无痕 编辑:程序博客网 时间:2024/04/28 06:08

HelloWorld小程序是每个java程序员都知道。让我们来看看我们能从这个小程序中学到什么。简单的开头能让我们更容易的学习复杂的知识。这篇文章读起来会很有趣,不仅仅适合入门级的java程序员。如果hello world对你意味着更多,欢迎评论。

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

1.为什么一切都以一个class开始

java程序构建在类之上,所有的方法和变量都在类中,这是因为它的面向对象特征。面向对象程序语言有很多优势,比如说模块化,可扩展性等等。

2.程序的入口——main方法

main方法是一个程序的入口并且是静态的。静态的意思是mai方法是这个类的一部分,而不是对象的一部分。

为什么要这样?为什么不用非静态的方法作为程序的入口?

如果一个方法是非静态的,那么要使用这个方法首先得创建一个对象出来。因为这个方法必须要在一个对象上被调用。作为入口

这是不现实的。因此,程序入口方法必须是静态的。

String[] args参数表明一个String类型的数组可以传入到程序中来帮助程序完成初始化。

3.HelloWorld的字节码形式

要执行某个程序,java文件首先要被编译成java字节码来存储在.class文件中。

字节码是什么样子的?

字节码本身是不可读的,但是我们借助一个16进制的编辑器,它看起来像下面这个样子:



我们能看到很多操作码(比如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_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 }
上面的代码包含两个方法:一个是默认的构造函数,由编译器自动生成,另一个是main方法

每个方法下面,都有一系列的指令,像是aload_0,invokespecial#1等等。每个指令是干什么的可以在

 Java bytecode instruction listings中查找。例如:aload_0将局部变量0的引入压入栈中,

getstatic获取一个类的静态变量值。注意getstatic指令后的#2指向了运行时的常量池。

常量池是JVM run-time data areas的一部分,让我们在来看看常量池,使用"javap -verbose"命令

即可。

除此之外,每个指令都以一个数字开头,像是0,1,4等等。在.class文件中,每一个

方法都有对应的字节数组。这些数字标识着操作码和它的参数在数组中存储的位置。

每个操作码是一个字节的长度,一个指令可以有0或多个参数。这也是为什么这些数字

是非连续的。

让我么你来执行下"javap -verbose"命令来深入认识下class

java -classpath . -verbose HelloWorld

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>":()Vconst #2 = Field#16.#17;//  java/lang/System.out:Ljava/io/PrintStream;const #3 = String#18;//  Hello Worldconst #4 = Method#19.#20;//  java/io/PrintStream.println:(Ljava/lang/String;)Vconst #5 = class#21;//  HelloWorldconst #6 = class#22;//  java/lang/Objectconst #7 = Asciz<init>;const #8 = Asciz()V;const #9 = AscizCode;const #10 = AscizLineNumberTable;const #11 = Ascizmain;const #12 = Asciz([Ljava/lang/String;)V;const #13 = AscizSourceFile;const #14 = AscizHelloWorld.java;const #15 = NameAndType#7:#8;//  "<init>":()Vconst #16 = class#23;//  java/lang/Systemconst #17 = NameAndType#24:#25;//  out:Ljava/io/PrintStream;const #18 = AscizHello World;const #19 = class#26;//  java/io/PrintStreamconst #20 = NameAndType#27:#28;//  println:(Ljava/lang/String;)Vconst #21 = AscizHelloWorld;const #22 = Ascizjava/lang/Object;const #23 = Ascizjava/lang/System;const #24 = Ascizout;const #25 = AscizLjava/io/PrintStream;;const #26 = Ascizjava/io/PrintStream;const #27 = Ascizprintln;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 specification 中有写到:运行时常量池的功能类似传统编程语言中的符号表

尽管它比传统的符号表多一个更广泛的数据。

invokespecial#1指令中的#1指向了常量池中的#1。常量是Method #6.#15。从数字中我们可以

递归的得到最终的常量。

LineNumberTable为debugger提供了一个标记,它指出字节码指令和java源文件的对应关系。

例如:源码中的第9行和main方法中的字节码0对应,第10行和字节码8对应。

对于字节码如果你想知道更多,可以创建并编译一个更复杂的类来看一看。

4.在JVM中是如何被执行的

现在的问题是JVM如何加载一个类并执行它的main方法?

在main方法执行之前,jvm需要三步:

1.加载——将一个类或者接口的二进制形式加载到jvm

2.链接——将二进制形式的数据链接到jvm运行时,其中链接这一步又包括三步:验证、准备和可选的方案。验证保证了类/接口结构是正确的。准备阶段是为类或者接口开辟内存。解析符号引用。

3.初始化类——初始化类变量,并分配默认值。


加载动作由java的classloader完成,当虚拟机启动的时候,有可能用到三种类加载器:

  1. Bootstrap class loader:加载位于/jre/lib目录下的核心java类库。该加载器是jvm的核心部分由native code完成。
  2. Extensions class loader: 加载位于扩展目录的类库(比如 /jar/lib/ext).
  3. System class loader: 加载位于CLASSPATH下的类库。

因此helloworld类被system class loader加载器加载,当main方法被执行的时候,将触发加载、链接、初始化该类及其依赖的类

最后,main方法针被压入jvm的栈,与此同时程序计数器被设置。然后程序计数器指示将println方法压栈,当main方法完成的时候,会从栈中弹出,该方法执行完毕。

0 0
原创粉丝点击