我們可以從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()方法完成後,它將被彈出堆棧,執行結束。

發佈了37 篇原創文章 · 獲贊 6 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章