从HelloWorld中我们能学到什么

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完成,当虚拟机启动的时候,有可能用到三种类加载器:

  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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值