天天看點

深入了解Java HelloWorld

HelloWorld是每個Java程式員都知道的程式。它很簡單,但是簡單的開始可以引導你去深入了解更複雜的東西。這篇文章将探究從這個HelloWorld這個簡單程式中可以學到的東西。如果你對HelloWorld有獨到的了解,歡迎留下你的評論。

  HelloWorld.java

?

1

2

3

4

5

6

7

8

9

10

11

12

13

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方法是程式的入口,并且是靜态方法。static關鍵字意味着這個方法是類的一部分,而不是執行個體對象的一部分。為什麼會這樣呢? 為什麼我們不用一個非靜态的方法作為程式的入口呢?

  如果一個方法不是靜态的,那麼對象需要先被建立好以後才能使用這個方法。因為這個方法必須要在一個對象上調用。對于一個入口來說,這是不現實的。是以,程式的入口方法是靜态的。

  參數 “String[] args”表明可以将一個字元串數組傳遞給程式來幫助程式初始化。

 3、HelloWorld程式的位元組碼

  為了執行這個程式,Java檔案首先被編譯成Java位元組碼存儲到.class檔案中。那麼位元組碼看起來是什麼樣的呢?位元組碼本身是不可讀的,如果我們使用一個二進制編輯器打開,它看起來就像下面那樣:

  在上面的位元組碼中,我們可以看到很多的操作碼(比如CA、4C等等),它們中的每一個都有一個對應的助記碼(比如下面例子中的aload_0)。操作碼是不可讀的,但是可以使用javap來檢視.class檔案的助記形式。

  對于類中的每個方法執行“javap -c”可以輸出反彙編代碼。反彙編代碼即組成Java位元組碼的指令。

?

1

javap -classpath .  -c HelloWorld

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

Compiled from

"HelloWorld.java"

public

class

HelloWorld

extends

java.lang.Object{

public

HelloWorld();

Code:

:   aload_0

1

:   invokespecial   #

1

;

//Method java/lang/Object."<init>":()V

4

:  

return

public

static

void

main(java.lang.String[]);

Code:

:   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個位元組長度,并且指令可以有0個或多個參數。這就是為什麼這些數字不是連續的原因。

  現在,我們使用“javap -verbose”這個指令來進一步觀察這個類。

?

1

javap -classpath . -verbose HelloWorld

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

Compiled from

"HelloWorld.java"

public

class

HelloWorld

extends

java.lang.Object

SourceFile:

"HelloWorld.java"

minor version:

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

:   aload_0

1

:   invokespecial   #

1

;

//Method java/lang/Object."<init>":()V

4

:  

return

LineNumberTable:

line

2

:

public

static

void

main(java.lang.String[]);

Code:

Stack=

2

, Locals=

1

, Args_size=

1

:   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

:

line

10

:

8

}

  引用JVM規範中的描述:運作期常量池提供的功能類似傳統程式設計語言的符号表所起作用, 盡管它比傳統的符号表包含的内容更廣範。

  “invokespecial #1″指令中的“#1″指向常量池中的#1常量。這個常量是 “Method #6.#15;”。通過這個數字,我們可以遞歸地得到最終的常量。

  LineNumberTable為調試器提供了用來訓示Java源代碼與位元組碼指令之間的對應資訊。例如,Java源代碼中的第9行對應于main方法中的位元組碼0,并且第10行對應于位元組碼8。

  如果想要了解更多關于位元組碼的内容,可以建立一個更加複雜的類進行編譯和檢視,HelloWorld真的隻是一個開始。

 4、HelloWorld在JVM中是如何執行的?

  現在的問題是JVM是怎樣加載這個類并調用main方法?

  在main方法執行之前, JVM需要加載、連結以及初始化這個類。

  1. 加載将類/接口的二進制形式裝入JVM中。

  2. 連結将二進制類型的資料融入到JVM的運作時。連結由3個步驟組成:驗證、準備、以及解析(可選)。驗證確定類、接口在結構上是正确的;準備涉及到為類、接口配置設定所需要的記憶體;解析是解析符号引用。

  3. 最後,初始化為類變量配置設定正确的初始值。

  加載工作是由Java類加載器來完成的。當JVM啟動時,會使用下面三個類加載器:

  1. Bootstrap類加載器:加載位于/jre/lib目錄下的核心Java類庫。它是JVM核心的一部分,并且使用本地代碼編寫。
  2. 擴充類加載器:加載擴充目錄中的代碼(比如/jar/lib/ext)。
  3. 系統類加載器:加載在CLASSPATH上的代碼。

  是以,HelloWorld類是由系統加載器加載的。當main方法執行時,它會觸發加載其它依賴的類,進行連結和初始化。前提是它們已經存在。

  最後,main()幀被壓入JVM堆棧,并且程式計數器(PC)也進行了相應的設定。然後,PC訓示将println()幀壓入JVM堆棧棧頂。當main()方法執行完畢會被彈出堆棧,至此執行過程就結束了。

 參考文檔

  1. 加載
  2. 類加載機制
  3. 類加載器

  原文連結: programcreek 翻譯: ImportNew.com - 黃飛飛

http://www.admin10000.com/document/3828.html