9.5 我們從Java的hello world中學(xué)到什么

這是每個(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é)碼

我們可以在上面的字節(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)變量。

代碼執(zhí)行

通過(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í)行完成。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容