在java世界裏,一切皆物件。從某種意義上來說,java有兩種物件:範例物件和Class物件。每個類的執行時的型別資訊就是用Class物件表示的。它包含了與類有關的資訊。其實我們的範例物件就通過Class物件來建立的。Java使用Class物件執行其RTTI(執行時型別識別,Run-Time Type Identification),多型是基於RTTI實現的。
每一個類都有一個Class物件,每當編譯一個新類就產生一個Class物件,基本型別 (boolean, byte, char, short, int, long, float, and double)有Class物件,陣列有Class物件,就連關鍵字void也有Class物件(void.class)。Class物件對應着java.lang.Class類,如果說類是物件抽象和集合的話,那麼Class類就是對類的抽象和集合。
Class類沒有公共的構造方法,Class物件是在類載入的時候由Java虛擬機器以及通過呼叫類載入器中的 defineClass 方法自動構造的,因此不能顯式地宣告一個Class物件。一個類被載入到記憶體並供我們使用需要經歷如下三個階段:
載入,這是由類載入器(ClassLoader)執行的。通過一個類的全限定名來獲取其定義的二進制位元組流(Class位元組碼),將這個位元組流所代表的靜態儲存結構轉化爲方法去的執行時數據介面,根據位元組碼在java堆中生成一個代表這個類的java.lang.Class物件。
鏈接。在鏈接階段將驗證Class檔案中的位元組流包含的資訊是否符合當前虛擬機器的要求,爲靜態域分配儲存空間並設定類變數的初始值(預設的零值),並且如果必需的話,將常數池中的符號參照轉化爲直接參照。
初始化。到了此階段,才真正開始執行類中定義的java程式程式碼。用於執行該類的靜態初始器和靜態初始塊,如果該類有父類別的話,則優先對其父類別進行初始化。
所有的類都是在對其第一次使用時,動態載入到JVM中的(懶載入)。當程式建立第一個對類的靜態成員的參照時,就會載入這個類。使用new建立類物件的時候也會被當作對類的靜態成員的參照。因此java程式程式在它開始執行之前並非被完全載入,其各個類都是在必需時才載入的。這一點與許多傳統語言都不同。動態載入使能的行爲,在諸如C++這樣的靜態載入語言中是很難或者根本不可能複製的。
在類載入階段,類載入器首先檢查這個類的Class物件是否已經被載入。如果尚未載入,預設的類載入器就會根據類的全限定名查詢.class檔案。在這個類的位元組碼被載入時,它們會接受驗證,以確保其沒有被破壞,並且不包含不良java程式碼。一旦某個類的Class物件被載入記憶體,我們就可以它來建立這個類的所有物件。
有三種獲得Class物件的方式:
對類的範例化 Test_22 obj = new Test_22();
介紹
《Java虛擬機器規範》中明確說明:「儘管所有的方法區在邏輯上是屬於堆的一部分,但一些簡單的實現可能不會選擇去進行垃圾收集或者進行壓縮。」但對於HotSpotJVM而言,方法區還有一個別名叫做Non-Heap(非堆),目的就是要和堆分開。所以,方法區看作是一塊獨立於Java堆的記憶體空間。
設定方法區記憶體大小
方法區所儲存的內容
1、型別資訊
對每個載入的型別(類class、介面interface、列舉enum、註解annotation),JVM在方法區中儲存以下型別資訊:
2、域資訊
JVM必須在方法區中儲存型別的所有域的相關資訊以及域的宣告順序。
域的相關資訊包括:域名稱、域型別、域修飾符(public,private,protected, static, final, volatile, transient的某個子集)
3、方法資訊
JVM必須儲存所有方法的以下資訊,同域資訊一樣包括宣告順序:
4、靜態變數
5、執行時常數池
常數池
執行時常數池
public class Test extends HashMap implements Serializable {
private String name = "";
private int x = 1;
public Test(String name) {
this.name = name;
}
public static void main(String[] args) {
Test haha = new Test(null);
int nameLength = haha.getNameLength();
System.out.println(nameLength);
}
public int getNameLength() {
int y = 0;
try {
y = name.length();
} catch (NullPointerException e) {
System.out.println("空指針異常");
e.printStackTrace();
}
return y;
}
}
Classfile /D:/ideaFiles/Algorithm/out/production/Algorithm/com/lx/mySort/Test.class
Last modified 2020-8-12; size 1145 bytes
MD5 checksum 8f9825153f3fa6f2042785c0df59703b
Compiled from "Test.java"
//類資訊
public class com.lx.mySort.Test extends java.util.HashMap implements java.io.Serializable
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #15.#44 // java/util/HashMap."<init>":()V
#2 = String #45 //
...
{
//域資訊
private java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE
private int x;
descriptor: I
flags: ACC_PRIVATE
//方法資訊
...
public int getNameLength();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: iconst_0
1: istore_1
2: aload_0
3: getfield #3 // Field name:Ljava/lang/String;
6: invokevirtual #10 // Method java/lang/String.length:()I
9: istore_1
10: goto 26
13: astore_2
14: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
17: ldc #12 // String 空指針異常
19: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
22: aload_2
23: invokevirtual #14 // Method java/lang/NullPointerException.printStackTrace:()V
26: iload_1
27: ireturn
//異常表
Exception table:
from to target type
2 10 13 Class java/lang/NullPointerException
LineNumberTable:
line 26: 0
line 28: 2
line 32: 10
line 29: 13
line 30: 14
line 31: 22
line 33: 26
LocalVariableTable:
Start Length Slot Name Signature
14 12 2 e Ljava/lang/NullPointerException;
0 28 0 this Lcom/lx/mySort/Test;
2 26 1 y I
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 13
locals = [ class com/lx/mySort/Test, int ]
stack = [ class java/lang/NullPointerException ]
frame_type = 12 /* same */
}
SourceFile: "Test.java"
演進過程
棧針
區域性變數表
動態連線 直接地址
返回地址 回覆 回復現場
當一個方法被執行後,有兩種方式退出這個方法。第一種方式是執行引擎遇到任意一個方法返回的位元組碼指令,這時候可能會有返回值傳遞給上層的方法呼叫者,是否有返回值和返回值的型別將根據遇到何種方法返回指令來決定,這種退出方法的方式稱爲正常完成出口(Normal
Method Invocation Completion)。
另外一種退出方式是,在方法執行過程中遇到了異常,並且這個異常沒有在方法體內得到處理,無論是Java虛擬機器內部產生的異常,還是程式碼中使用athrow位元組碼指令產生的異常,只要在本方法的異常表中沒有搜尋到匹配的例外處理器,就會導致方法退出,這種退出方法的方式稱爲異常完成出口(Abrupt
Method Invocation Completion) 。一個方法使用異常完成出口的方式退出,是不會給它的上層呼叫者產生任何返回值的。
無論採用何種退出方式,在方法退出之後,都需要返回到方法被呼叫的位置,程式才能 纔能繼續執行,方法返回時可能需要在棧幀中儲存一些資訊,用來幫助恢復它的上層方法的執行狀態。一般來說,方法正常退出時,呼叫者的PC計數器的值就可以作爲返回地址,棧幀中很可能會儲存這個計數器值。而方法異常退出時,返回地址是要通過例外處理器表來確定的,棧幀中一般不會儲存這部分資訊。
方法退出的過程實際上等同於把當前棧幀出棧,因此退出時可能執行的操作有:恢復上層方法的區域性變數表和運算元棧,把返回值(如果有的話)壓入呼叫者棧幀的運算元棧中,調整PC計數器的值以指向方法呼叫指令後面的一條指令等。
JVM執行main方法,內部是如何執行的
1、建立執行main方法需要的棧幀
2、將main方法的運算元棧指針賦值給執行緒的屬性:運算元棧
3、將main方法的區域性表指針賦值給給執行緒的屬性:區域性表指針
VM執行add方法,內部是怎麼做的
1、建立add的方法的棧幀
2、在add方法的棧幀中儲存main方法的位元組碼的下一行程式計數器(18)
3、執行緒的區域性表開始指針(main的)儲存至add方法的棧幀
4、執行緒的運算元棧開始指針(main的)儲存至add方法的棧幀
5、將add方法的區域性表指針賦值給執行緒的區域性表指針
6、將add方法的運算元棧指針賦值給執行緒的運算元棧指針