从HelloWorld中我们能学到什么
日期: 2014-12-26 分类: 个人收藏 334次阅读
HelloWorld小程序是每个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.为什么一切都以一个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>":()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 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完成,当虚拟机启动的时候,有可能用到三种类加载器:
- Bootstrap class loader:加载位于/jre/lib目录下的核心java类库。该加载器是jvm的核心部分由native code完成。
- Extensions class loader: 加载位于扩展目录的类库(比如 /jar/lib/ext).
- System class loader: 加载位于CLASSPATH下的类库。
因此helloworld类被system class loader加载器加载,当main方法被执行的时候,将触发加载、链接、初始化该类及其依赖的类
最后,main方法针被压入jvm的栈,与此同时程序计数器被设置。然后程序计数器指示将println方法压栈,当main方法完成的时候,会从栈中弹出,该方法执行完毕。
除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog
上一篇: 互联网公司该不该加班?
下一篇: java并发包的CAS操作
精华推荐