前言
Java 是門物件導向的開發語言,那麼我們自己編寫的 Java 類生成的物件是什麼樣的?它肯定儲存在虛擬機器的記憶體中,但它以怎樣的結構來儲存的呢?帶著疑問往下看看。
關於Klass
Java 層的開發可能不太熟悉 Klass,但肯定熟悉 class,我們只要知道 Klass 是 class 在 JVM 中的表示即可,即 Java class 對應 JVM Klass。C++ 中的繼承關係如下:
class MetaspaceObj
class Metadata
class Klass
複製程式碼
Klass 類用來描述 Java 類資訊,包括描述型別自身佈局、類名、父類、子類、兄弟類等等。
關於oop
按前面 class 對應的方式,那麼物件也應該有 JVM 內部與之相對應的表示吧?沒錯,就是 oop(ordinary object pointer),普通物件指標。它的定義如下:
typedef class oopDesc* oop;
複製程式碼
其中 oopDesc 類是所有 oop 的基類。在 JVM 中,不同的 oop 用於描述特定型別的物件。比如類物件用 instanceOopDesc,陣列用 arrayOopDesc。
Klass+oop模型
Java 物件在 JVM 中的結構如下,包括 header 和物件內容。如下圖中,左邊的是 instanceOopDesc,即一般的類物件,header 包括了標識和後設資料,標識用於儲存執行時記錄資訊,包括雜湊碼、GC鎖和執行緒鎖等等。而右邊的為 arrayOopDesc,即陣列物件,header 多了個 length,用於記錄陣列長度。
來個demo
public class Test {
private String[] flag = { "a", "b", "c" };
private String name = "test";
public static void main(String[] args) throws Exception {
Test test = new Test();
String _name = "test";
System.out.println(test.flag);
System.out.println(_name);
}
}
複製程式碼
物件結構
在上面程式中打個斷點,通過 jps 查出 pid,然後使用下面命令開啟 hsdb,根據 pid 連線到 JVM。
jhsdb hsdb
複製程式碼
檢視 main 執行緒的棧記憶體,我們主要是要拿到 Test 物件的地址,即0x000000008a105dd0
。
接著用 inspector 來檢視0x000000008a105dd0
地址對應的 oop,看到這個 oop 就是我們的 Test 類生成的物件結構了,包含了 mark 和 metadata。這裡可能會有個疑問,就是前面不是說陣列還有一個 length 來表示陣列長度的嗎?但圖中的 flag 陣列變數並沒有看到 length 啊。
其實陣列 oop 並不是沒有 length,而是 C++ 並沒有宣告這個變數,而是通過指標來直接將陣列長度儲存到對應的記憶體了,所以這裡是看不到的。通過下面具體的實現程式碼就能清楚瞭解到原因了。是不是我們就沒辦法看到這個長度值呢?並不是,下面繼續看如何來看這個值。
int length() const {
return *(int*)(((intptr_t)this) + length_offset_in_bytes());
}
void set_length(int length) {
*(int*)(((intptr_t)this) + length_offset_in_bytes()) = length;
}
複製程式碼
前面我們可以得到 flag 陣列 oop 的地址為0x000000008a105de8
。64位機器上_mark
為8位元組,_metadata
為4位元組,那麼將地址加12,得到0x000000008a105df4
。然後用 hsdb 命令列的 examine 得到地址的值,得到0x8a105e0800000003
,其中00000003
便是。
hsdb> examine 0x000000008a105df4
0x000000008a105df4: 0x8a105e0800000003
複製程式碼
-------------推薦閱讀------------
跟我交流,向我提問:
公眾號的選單已分為“讀書總結”、“分散式”、“機器學習”、“深度學習”、“NLP”、“Java深度”、“Java併發核心”、“JDK原始碼”、“Tomcat核心”等,可能有一款適合你的胃口。
歡迎關注: