我们可以从Java的HelloWorld中学到什么?
2014-02-28 16:02
651 查看
这是所有Java程序员知道的程序,它很简单,但是这样一个简单的开始可以带领我们更深的理解更多复杂的 概念。在这篇文章中,将探索我们可以从这个简单的程序中学到什么。
HelloWorld.java
1、为什么所有东西都从类开始?
Java程序被一个类构成,所有方法和字段都在一个类中。这是由于Java的面向对象的特征:一切对象都是一个类的实例。面向对象的编程语言对于面向函数式的编程语言而言有许多优点,例如更好的模块化,可扩展性等等。
2、为什么总是有一个“Main”方法?
Main方法是一个程序的入口且是静态的,静态意味着这个方法是类的一部分,而不是对象的一部分。
为什么这样做呢,我们为什么不用一个非静态的方法作为一个程序的入口?
如果一个方法是非静态的,我们要实用这个方法时必须首先创建一个对象,因为方法必须在一个对象上被调用。用于程序的入口,显然是不现实的。如果没有鸡,我们就得不到蛋,因此,程序入口方法应该是静态的。
参数“String[] args”表明可以传送一个字符串数组帮助程序的初始化。
3、HelloWorld的字节码
执行一个程序时,Java文件首先被编译成Java字节码文件存储在.class文件中,字节码文件时什么样的?字节码文件本身是不可读的,如果我们使用十六进制编辑器,它们看起来是这样的:
我们可以在上面的字节码中看到许多操作码(例如CA,4C等等),每个操作吗都有一个对应的助记码(例如下面例子中的aload_0),操作码是不可读的,但是我们可以使用javap去从.class文件中看到助记码。
“javap -c” 打印出每个类中方法的反汇编代码。反汇编代码意味着指令由java字节码组成。
上面的代码包含两个方法:一个是默认构造函数,它由编译器自动生成,另一个是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()方法完成后,它将被弹出堆栈,执行结束。
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()方法完成后,它将被弹出堆栈,执行结束。
相关文章推荐
- Java可以做什么及可以给我们带来什么?
- 我们能从Java的HelloWorld中学到什么?
- Java可以做什么及可以给我们带来什么?
- Java可以做什么及可以给我们带来什么?
- 我们能从Java的HelloWorld中学到什么?
- JAVA 学到什么水平就可以转战 Android 了?
- 从乌镇大佬“饭局的诱惑”中我们可以解读出什么?
- 一个java源文件中是否可以包含多个类(内部类除外),有什么限制?
- 一个“.java”文件中是否可以包含多个类(不是内部类)?有什么限制?
- java 中我们该学习什么? (转)
- 关于java环境的意义,以前只是配置,没有明白是什么意思可以看看。
- 世界上好玩的东西很多,不是什么东西都可以弄精通 的,但吃饭的家伙还真得精通。厨师不能靠意念炒菜、司机不能靠意念开车,你也不能靠意念写程序,人不能活 在想象之中,电脑帮我们写程序的科幻时代还没到来。学不好,是你没刻苦努力,与你的才智无关。
- JAVA 语言如何进行异常处理,关键字:throws,throw,try,catch,finally 分别代表什么意义?在 try 块中可以抛出异常吗?
- JAVA9将于2017年3月份发布,中国Java用户有什么反馈请赶紧提,我们一起整理给Oracle.
- 我们公司的用SANGFOR将内部员工的网封掉后,有员工破解掉,还可以上网,麻烦问下有没有什么办法给阻止掉》
- Java - Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别?
- java学习中springAOP—可以提升开发效率的神器,你知道什么吗?
- java有几种方法可以实现一个线程?用什么关键字修饰同步方法?
- Tuxera NTFS有什么优点可以让我们使用?
- Eclipse平台入门之一:什么是Eclipse,我们将开始介绍Java 开发环境(JDE)。