HelloWorld是每個(gè)Java程序員都知道的程序实愚。它很簡(jiǎn)單,但是簡(jiǎn)單的開始可以引導(dǎo)你去深入了解更復(fù)雜的東西。這篇文章將探究從這個(gè)HelloWorld這個(gè)簡(jiǎn)單程序中可以學(xué)到的東西。如果你對(duì)HelloWorld有獨(dú)到的理解逊躁,歡迎留下你的評(píng)論。
**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程序是從類開始構(gòu)建的, 每個(gè)方法和字段都必須在類里面核芽。這是由于Java面向?qū)ο蟮奶匦? 一切都是對(duì)象,它是類的一個(gè)實(shí)例酵熙。面向?qū)ο缶幊陶Z(yǔ)言相比函數(shù)式編程語(yǔ)言有許多的優(yōu)勢(shì)轧简,比如更好的模塊化、可擴(kuò)展性等等绿店。
2****吉懊、為什么總有一個(gè)“main方法”?
main方法是程序的入口假勿,并且是靜態(tài)方法。static關(guān)鍵字意味著這個(gè)方法是類的一部分态鳖,而不是實(shí)例對(duì)象的一部分转培。為什么會(huì)這樣呢? 為什么我們不用一個(gè)非靜態(tài)的方法作為程序的入口呢?
如果一個(gè)方法不是靜態(tài)的浆竭,那么對(duì)象需要先被創(chuàng)建好以后才能使用這個(gè)方法浸须。因?yàn)檫@個(gè)方法必須要在一個(gè)對(duì)象上調(diào)用。對(duì)于一個(gè)入口來說邦泄,這是不現(xiàn)實(shí)的删窒。因此,程序的入口方法是靜態(tài)的顺囊。
參數(shù) “String[] args”表明可以將一個(gè)字符串?dāng)?shù)組傳遞給程序來幫助程序初始化肌索。
3、HelloWorld程序的字節(jié)碼
為了執(zhí)行這個(gè)程序特碳,Java文件首先被編譯成Java字節(jié)碼存儲(chǔ)到.class文件中诚亚。那么字節(jié)碼看起來是什么樣的呢?字節(jié)碼本身是不可讀的午乓,如果我們使用一個(gè)二進(jìn)制編輯器打開站宗,它看起來就像下面那樣:
在上面的字節(jié)碼中,我們可以看到很多的操作碼(比如CA益愈、4C等等)梢灭,它們中的每一個(gè)都有一個(gè)對(duì)應(yīng)的助記碼(比如下面例子中的aload_0)。操作碼是不可讀的蒸其,但是可以使用javap來查看.class文件的助記形式敏释。
對(duì)于類中的每個(gè)方法執(zhí)行“javap -c”可以輸出反匯編代碼。反匯編代碼即組成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)造器颂暇;另外一個(gè)是main方法。
接下來但惶,每個(gè)方法都有一系列的指令耳鸯。比如aload_0湿蛔、invokespecial #1等等∠嘏溃可以在Java字節(jié)碼指令集中查到每個(gè)指令的功能阳啥,例如aload_0用來從局部變量0中加載一個(gè)引用到堆棧,getstatic用來獲取類的一個(gè)靜態(tài)字段值财喳〔斐伲可以注意到,getstatic指令之后的“#2″指向的是運(yùn)行期常量池耳高。常量池是JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)之一扎瓶。我們可以通過“javap -verbose”命令來查看常量池。
另外, 每個(gè)指令從一個(gè)數(shù)字開始,比如0、1踏堡、4等等。在.class文件中误证,每個(gè)方法都有一個(gè)對(duì)應(yīng)的字節(jié)碼數(shù)組。這些數(shù)字對(duì)應(yīng)于存儲(chǔ)每個(gè)操作碼及其參數(shù)的數(shù)組的下標(biāo)修壕。每個(gè)操作碼都是1個(gè)字節(jié)長(zhǎng)度愈捅,并且指令可以有0個(gè)或多個(gè)參數(shù)。這就是為什么這些數(shù)字不是連續(xù)的原因慈鸠。
現(xiàn)在蓝谨,我們使用“javap -verbose”這個(gè)命令來進(jìn)一步觀察這個(gè)類。
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)行期常量池提供的功能類似傳統(tǒng)編程語(yǔ)言的符號(hào)表所起作用, 盡管它比傳統(tǒng)的符號(hào)表包含的內(nèi)容更廣范林束。
“invokespecial #1″指令中的“#1″指向常量池中的#1常量像棘。這個(gè)常量是 “Method #6.#15;”。通過這個(gè)數(shù)字壶冒,我們可以遞歸地得到最終的常量缕题。
LineNumberTable為調(diào)試器提供了用來指示Java源代碼與字節(jié)碼指令之間的對(duì)應(yīng)信息。例如胖腾,Java源代碼中的第9行對(duì)應(yīng)于main方法中的字節(jié)碼0烟零,并且第10行對(duì)應(yīng)于字節(jié)碼8。
如果想要了解更多關(guān)于字節(jié)碼的內(nèi)容咸作,可以創(chuàng)建一個(gè)更加復(fù)雜的類進(jìn)行編譯和查看锨阿,HelloWorld真的只是一個(gè)開始。
4记罚、HelloWorld在JVM中是如何執(zhí)行的墅诡?
現(xiàn)在的問題是JVM是怎樣加載這個(gè)類并調(diào)用main方法?
在main方法執(zhí)行之前, JVM需要加載桐智、鏈接以及初始化這個(gè)類末早。
1.加載將類/接口的二進(jìn)制形式裝入JVM中烟馅。
2.鏈接將二進(jìn)制類型的數(shù)據(jù)融入到JVM的運(yùn)行時(shí)。鏈接由3個(gè)步驟組成:驗(yàn)證然磷、準(zhǔn)備郑趁、以及解析(可選)。驗(yàn)證確保類姿搜、接口在結(jié)構(gòu)上是正確的寡润;準(zhǔn)備涉及到為類、接口分配所需要的內(nèi)存舅柜;解析是解析符號(hào)引用梭纹。
3.最后,初始化為類變量分配正確的初始值致份。
加載工作是由Java類加載器來完成的栗柒。當(dāng)JVM啟動(dòng)時(shí),會(huì)使用下面三個(gè)類加載器:
1.Bootstrap類加載器:加載位于/jre/lib目錄下的核心Java類庫(kù)知举。它是JVM核心的一部分,并且使用本地代碼編寫太伊。
2.擴(kuò)展類加載器:加載擴(kuò)展目錄中的代碼(比如/jar/lib/ext)雇锡。
3.系統(tǒng)類加載器:加載在CLASSPATH上的代碼。
所以僚焦,HelloWorld類是由系統(tǒng)加載器加載的锰提。當(dāng)main方法執(zhí)行時(shí),它會(huì)觸發(fā)加載其它依賴的類芳悲,進(jìn)行鏈接和初始化立肘。前提是它們已經(jīng)存在。
最后名扛,main()幀被壓入JVM堆棧谅年,并且程序計(jì)數(shù)器(PC)也進(jìn)行了相應(yīng)的設(shè)置。然后肮韧,PC指示將println()幀壓入JVM堆棧棧頂融蹂。當(dāng)main()方法執(zhí)行完畢會(huì)被彈出堆棧,至此執(zhí)行過程就結(jié)束了弄企。
今天就分享這么多超燃,歡迎各位朋友在留言區(qū)評(píng)論,對(duì)于有價(jià)值的留言拘领,我都會(huì)一一回復(fù)的意乓。如果覺得文章對(duì)你有一丟丟幫助,請(qǐng)給我點(diǎn)個(gè)贊吧约素,讓更多人看到該文章届良。
另外笆凌,小編最近將收集的Java程序員進(jìn)階架構(gòu)師和面試的資料做了一些整理,免費(fèi)分享給每一位學(xué)習(xí)Java的朋友伙窃,需要的可以進(jìn)群:751827870菩颖,歡迎大家進(jìn)群和我一起交流。
本文由博客一文多發(fā)平臺(tái) OpenWrite 發(fā)布为障!