這是每個(gè)Java程序員都知道的程序。這很簡(jiǎn)單,但是一個(gè)簡(jiǎn)單的開(kāi)始可以深入復(fù)雜的概念。這篇文章中,我將探討從這個(gè)簡(jiǎn)單的程序中可以學(xué)到什么。
HelloWorld.java
public class HelloWorld {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("Hello World");
}
}
1. 為什么都從一個(gè)類(lèi)開(kāi)始
Java程序都是從類(lèi)開(kāi)始,任何方法和字段都必須在一個(gè)類(lèi)中。這是由于面向?qū)ο蟮奶匦裕阂磺卸际且粋€(gè)類(lèi)的實(shí)例。面向?qū)ο缶幊瘫群瘮?shù)式編程有很多優(yōu)點(diǎn),比如更好的模塊化,可擴(kuò)展性。
2.為什么那總有一個(gè)main方法?
main 方法是程序的入口,它是靜態(tài)的。“static”表示該方法是其類(lèi)的一部分,而不是對(duì)象的一部分。
這是為什么?為什么不把非靜態(tài)方法作為程序入口?
如果方法不是靜態(tài)的,那么就需要先創(chuàng)建個(gè)對(duì)象來(lái)使用這個(gè)方法,因?yàn)楸仨氃趯?duì)象上調(diào)用這個(gè)方法。為了入口目的,這不現(xiàn)實(shí)的。我們不能得到?jīng)]有雞的雞蛋。因此程序的入口方法是靜態(tài)的。
參數(shù)String [] args 表示可以將一個(gè)字符串?dāng)?shù)組發(fā)送到程序來(lái)幫助程序初始化。
3.3. HelloWorld的字節(jié)碼
要執(zhí)行程序,Java文件首先被編譯為存儲(chǔ)在.class文件中的java字節(jié)碼。字節(jié)代碼是什么樣的?字節(jié)碼本身不可讀的。如果我們使用十六進(jìn)制編輯器,它顯示如下:

我們可以在上面的字節(jié)碼中看到很多操作碼(例如CA、4C等),每個(gè)操作碼都具有相應(yīng)的助記碼(例如下面的例子的aload_0)。操作碼是不可讀的。但是我們可以用javap查看.class的助記符形式。(相當(dāng)于匯編)
“javap -c”打印出類(lèi)中每個(gè)方法的反匯編代碼。 反匯編的代碼意味著構(gòu)成Java字節(jié)碼的指令。
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
}
上面代碼包含兩個(gè)方法:一個(gè)是由編譯器推斷的默認(rèn)構(gòu)造函數(shù),另外一個(gè)是main方法。
在每個(gè)方法之下,都有一系列指令,例如aload_0,invokespecial#1等。每個(gè)指令可以在Java字節(jié)碼指令列表中查找。例如 :aload_0將本地變量0的引用加載到堆棧中。getstatic獲取類(lèi)的靜態(tài)字段值。請(qǐng)注意,getstatic指令后的“#2”指向運(yùn)行常量池。常量池是JVM的運(yùn)行數(shù)據(jù)區(qū)之一。運(yùn)行javap -verbose命令來(lái)看下常量池。
另外每個(gè)指令都是以一個(gè)數(shù)字開(kāi)頭,如0,1,4等。在.class文件中,每個(gè)方法都有一個(gè)對(duì)應(yīng)的字節(jié)碼數(shù)組。這些數(shù)字對(duì)于存儲(chǔ)每個(gè)操作碼及其參數(shù)的數(shù)組索引。每個(gè)操作碼長(zhǎng)度為1個(gè)字節(jié),指令可以有0個(gè)或多個(gè)參數(shù),這就是為什么這些數(shù)字不連續(xù)的原因。
我們可以使用"javap -verbose"來(lái)進(jìn)一步了解類(lèi)。
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規(guī)范:運(yùn)行時(shí)常量池為編程語(yǔ)言提供類(lèi)似符號(hào)表的函數(shù),雖然它比符號(hào)表更寬的數(shù)據(jù)范圍。
“invocationspecial#1”指令中的“#1”指向常量池中的#1常數(shù)。 常數(shù)是“方法#6.#15;”。 從數(shù)字,我們可以遞歸得到最終的常數(shù)。
LineNumberTable向調(diào)試器提供信息,以指示哪個(gè)Java源代碼對(duì)應(yīng)哪個(gè)字節(jié)碼指令。例如,Java源代碼中的第9行對(duì)應(yīng)main方法中的字節(jié)碼0,第10行對(duì)應(yīng)字節(jié)碼8。
如果你想更多了解字節(jié)碼,你可以創(chuàng)建和編譯一個(gè)更復(fù)雜的類(lèi)來(lái)看看。Hello world真是這樣做的起點(diǎn)。
4.如何在JVM中執(zhí)行
現(xiàn)在的問(wèn)題是JVM如何加載類(lèi)和調(diào)用main方法。
在main方法被執(zhí)行之前,JVM需要1)加載 2)鏈接 3)初始化類(lèi)。
1)加載類(lèi)或接口的二進(jìn)制形式到JVM中。
2)鏈接將二進(jìn)制類(lèi)型數(shù)據(jù)并入JVM的運(yùn)行時(shí)狀態(tài)。
鏈接包含三個(gè)步驟:驗(yàn)證,準(zhǔn)備和可選解決方案。驗(yàn)證確保類(lèi)和接口在結(jié)構(gòu)上是正確的;準(zhǔn)備涉及分配類(lèi)或接口所需的內(nèi)存;決議解決了符號(hào)引用。
3)最后初始化使用適當(dāng)?shù)某跏贾捣峙漕?lèi)變量。

通過(guò)Java 的ClassLoaders進(jìn)行加載,當(dāng)JVM被啟動(dòng)時(shí)候,使用三類(lèi)加載器:
1、Bootstrap class loader:加載位于/jre/lib目錄下的java核心類(lèi)庫(kù)。它是JVM核心的一部分,用本地代碼編寫(xiě)的。
2、Extensions class loader:加載位于擴(kuò)展目錄的代碼(比如/jar/lib/ext).
3、System class loader:加載在CLASSPATH中發(fā)現(xiàn)的代碼。
所以HelloWorld類(lèi)是由系統(tǒng)類(lèi)加載器加載的。 當(dāng)main方法被執(zhí)行時(shí),它會(huì)觸發(fā)其他依賴(lài)類(lèi)的加載,鏈接和初始化(如果它們存在)。
最后,將main()框架推入JVM堆棧,并相應(yīng)地設(shè)置程序計(jì)數(shù)器(PC)。 PC然后指示將println()幀推送到JVM堆棧。 當(dāng)main()方法完成時(shí),它將從堆棧彈出并執(zhí)行完成。