您的位置:首页 > 编程语言 > Java开发

我们可以从Java的HelloWorld中学到什么?

2014-02-28 16:02 651 查看
这是所有Java程序员知道的程序,它很简单,但是这样一个简单的开始可以带领我们更深的理解更多复杂的 概念。在这篇文章中,将探索我们可以从这个简单的程序中学到什么。



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程序被一个类构成,所有方法和字段都在一个类中。这是由于Java的面向对象的特征:一切对象都是一个类的实例。面向对象的编程语言对于面向函数式的编程语言而言有许多优点,例如更好的模块化,可扩展性等等。

 

2、为什么总是有一个“Main”方法?

Main方法是一个程序的入口且是静态的,静态意味着这个方法是类的一部分,而不是对象的一部分。

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

如果一个方法是非静态的,我们要实用这个方法时必须首先创建一个对象,因为方法必须在一个对象上被调用。用于程序的入口,显然是不现实的。如果没有鸡,我们就得不到蛋,因此,程序入口方法应该是静态的。

参数“String[] args”表明可以传送一个字符串数组帮助程序的初始化。

 

3、HelloWorld的字节码

执行一个程序时,Java文件首先被编译成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_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字节码指令清单。例如,aload_0把局部变量0的引用压人堆栈,getstatic取得静态获取类的一个静态字段值。注意 在getstatic指令之后的 #2指向运行时的常量池。常量池是JVM中的一个运行时数据池,可以通过“ javap -verbose”产看常量池。

另外,每条指令从一个数字开始,例如0,1,4等。在.class文件中,每个函数有一个相应的字节码数组,这些数字对应存储这些操作码和它们的参数的数组索引。每个操作码是1 byte的长度,指令可以有0个或者多个参数,这就是为什么这些数字不是连续的。

现在让我们使用“ javap -verbose”去更深入的看看类。

javap -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的运行规范中写道:运行常量池是一个类似传统编程语言的函数符号表,尽管它包含了一个比典型符号表更广泛的数据。

在“invokespecial#1”指令中的#1指向常量池中的#1常量。常量是 #6 #15,通过这些数字,我们就可以递归的找到最终的常量。

 LineNumberTable可以给调试器提供信息以表明Java源代码对应于哪一行的字节码指令。例如Java源代码的第九行对应于字节码0,第10行对应于字节码8.

如果你想知道更多关于字节码的信息,你可以创建和编译一个更加复杂的类来看看,HelloWorld只是这方面的开始。

4、如何在JVM中执行?

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

在Main方法执行之前,JVM需要三步

1)加载  。2)链接。3)初始化这个类。  

1)将一个类或者接口的二进制形式加载到JVM

2)链接包含二进制类型的数据到运行态的JVM,链接包括三个步骤:验证,准备和可选的解决方案。验证是确保类和接口的格式和结构正确;准备包括给类和接口分配所需要的内存;决议解析符号引用。

3)初始化给类变量分配合适的初始值。



 这个工作由Java的类加载器(Classloader)完成,当JVM启动的时候,有三个类加载器被使用:

1、Bootstrap class loader:加位于/jre/lib目录下的Java核心库,这是JVM核心的一部分,是用原生代码编写的。

2、Extensions class loader:加载扩展目录中的代码(例如:/jar/lib/ext)

3、System class loader:加载CLASSPATH中的代码

所以HelloWorld 类被系统的类加载器所加载。当Main方法执行的时候,如果还有其他相关类存在,类加载器将会加载、链接、和初始化它们。

最后。Main() 框架 被压入JVM堆栈,并相应的设置程序计数器(PC)。程序计数器指示将Println()框架压入JVM堆栈。当Main()方法完成后,它将被弹出堆栈,执行结束。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐