這是每個Java程序員都知道的。雖然簡單,但是從一個簡單的問題可以引入更深的思考。在這篇文章中,我們將討論這個簡單的程序。如果能更多的幫到你,請留下寶貴的意見。
HelloWorld.java
1、為什么一切都開始于一個類?
Java程序是由類組成,一個類包含方法和屬性。這是由于它的面向對象的特征:一切皆對象,每個對象都是一個類的實例。面向對象編程有很多優勢,比如更好的模塊化,擴展性強等
2、為什么總有一個“main”方法?
“main”方法是程序的入口,它是靜態的。 “static”是指該方法是類的一部分,而不是對象的一部分。
這是為什么?我們為什么不把一個非靜態方法作為程序的入口?
如果方法不是靜態的,那么需要創建一個對象后才能使用方法。因為必須用對象去調用方法。對于程序的入口,這是不現實的。所以,程序的入口方法是靜態的。
參數“String[] args”表示一個字符串數組可以被傳入到該程序,用來初始化程序。
3、HelloWorld的字節碼
執行這個程序,Java文件首先編譯為java字節碼儲存在.class文件里。
字節碼是什么樣子的呢?
首先,字節碼本身是無法讀取。如果我們用一個十六進制編輯器打開,它看起來像下面這樣:
我們能看到很多操作碼(比如 CA、4C 等)在字節碼上,它們每個都有一個相應的助記碼(比如,aload_0 在下面的例子中)。操作碼是不可讀的,但我們可以用javap命令查看.class文件的助記符形式。
“javap -C”打印出每個方法的反匯編代碼。反匯編代碼的意思是包括Java字節碼的說明。
- 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,等
下面的每個方法,也有說明,如aload_0,invokespecial#1,等指令可以在java指令清單里 查到 。例如,aload_0指令是加載一個從棧中引用的本地變量0,getstatic 指令獲取一個類的靜態字段值。注意“#2” 指令在getstatic指令后指向運行常量池。常量池是一個JVM運行時數據區, 查看 。我們可以用“javap -verbose”命令來查看常量池。
此外,每個指令開始于一個數字,如0,1,4等。在.class文件中,每個方法都有一個對應的字節碼數組。這些數字對應的每一個操作碼和它的參數都存儲在數組中的索引中。每個操作碼為1個字節,指令可以有0個或多個參數。這就是為什么數字是不連續的。
現在,我們可以用“javap -verbose” 查看.class文件進一步研究。
- 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常量在常量池中.常量是”Method #6.#15;“從數字上看,我們就可以按遞歸方式來得到最終的常量。
LineNumberTable提供用來調試java源代碼對應字節碼的行數信息例如,在main方法里Java源代碼第9行對應字節碼0,第10行對應字節碼8。
如果你想知道更多關于字節碼,您可以創建和編譯一個更復雜的類來看一看。HelloWorld確實是個很簡單的例子。
4、它是如何在JVM中執行?
現在的問題是如何JVM加載類并調用main方法?
在main方法執行之前,JVM需要分三步走加載、連接以及初始化該類。1)加載二進制的類和接口到jvm中。 2)連接合并二進制數據到正在運行狀態的jvm。連接有三步構成,驗證、準備、解析。驗證確保了類/接口在結構上正確的;準備工作包括所需要的類/接口分配內存;解析符號引用。最后3)初始化變量并初始化值
這個裝載工作是由Java類加載器完成的。當JVM啟動時,3個類加載器被使用:
1.引導類加載器:加載位于/ jre / lib目錄的核心Java庫。這是jvm核心的一部分,并且是原生的代碼。
2.擴展類加載器:加載代碼的擴展目錄(例如,/jar/ lib / ext目錄)。
3.系統類加載器:在CLASSPATH中找到負載代碼。
所以HelloWorld類是由系統類加載器加載。當執行的主要方法,它會觸發加載,鏈接和其他相關的類的初始化( 查看 ),如果它們存在。
最后,main()的幀被加載到jvm堆棧,程序計數器(PC)被相應地設置。程序計數器然后指示println()幀的加載到JVM堆棧。當main()方法完成后,它會從堆棧中彈出執行完成。
參考文獻:
1、 負載
2、 類加載機制
3、 類加載器
via: http://www.programcreek.com/2013/04/what-can-you-learn-from-a-java-helloworld-program/
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
