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

这是所有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()方法完成后,它将被弹出堆栈,执行结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值