讀書筆記:《深入理解java虛擬機》(一)

目錄

1. 運行時數據區域

2.對象(HotSpot虛擬機爲例)


1. 運行時數據區域

a.程序計數器:線程私有的內存空間,可看作當前線程所執行的字節碼的行號指示器。字節碼解釋器工作時就是通過改變計數器的值來選取下條需要執行的字節碼指令,分支,循環,跳轉,異常處理,線程恢復等功能都需要計數器來完成。各個線程之間計數器互不影響,獨立存儲。如果線程正在執行的是java方法,計數器記錄的是正在執行的虛擬機字節碼指令地址;如果執行的是native方法,則計數器值爲空。

b.java虛擬機棧:線程私有空間,生命週期與線程相同。每個java方法在執行的同時都會創建一個棧幀用於存儲局部變量表,操作數棧,動態鏈接,方法出口等信息。每一個方法的調用到執行完成過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。如果線程請求的棧深度大於虛擬機所允許的深度,則拋出StackOverflowError異常,如果虛擬機棧可以動態擴展,擴展時仍無法申請到足夠的內存,就會拋出OutOfMemoryError異常。

局部變量表存放編譯期可知的各種基本類型,對象引用和returnAddress類型(指向一條字節碼指令地址)。其中64位長度的long和double類型數據佔用2個局部變量空間(Slot),局部變量表所需內存空間在編譯期完成分配。

c.本地方法棧:虛擬機棧執行java方法,本地方法棧執行Native方法。與虛擬機棧一樣,會拋出異常。

d.java堆:是被所有線程共享的內存區域,在虛擬機啓動時創建。此區域唯一功能是存放對象實例,幾乎所有對象實例都在這裏分配內存。java堆可以處於物理上不連續的內存空間,可以是固定大小,也可是可擴展的。如果堆沒有內存完成實例分配並無法再擴展時,將會拋出OutOfMemoryError。

e.方法區:線程共享區域,存儲已被虛擬機加載的類信息,常量,靜態變量,即時編譯器編譯後的代碼等數據。

f:運行時常量池:方法區的一部分。Class文件中除了有類的版本,字段,方法接口等描述信息外,還有一項信息是常量池,用於存放編譯期生成的各種字面量和符號引用,這部分內容在類加載後進入方法區的運行時常量池存放。運行期間也可能將新的常量放入池中,這種特性利用得較多的是String類的intern()方法(把字符串常量池從方法區中剝離出來,存放在堆空間中)。

g.直接內存:並不是虛擬機運行時數據區的一部分,在jdk1.4中新加入了NIO類,引入一種基於通道與緩存區的I/O方式,它可以使用Native函數庫直接分配堆外內存,通過一個存儲在java堆中的DirectByteBuffer對象作爲這塊內存的引用,避免了在java堆和Native堆中來回負制數據。

 

2.對象(HotSpot虛擬機爲例)

  a.對象的創建(普通對象)

虛擬機遇到一條new指令時,首先檢查這個指令參數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已被加載,解析和初始化過,如果沒有,則先執行相應的類加載過程。檢查通過後,虛擬機將爲新生對象分配內存(所需內存在類加載完成後可確定)。爲對象分配內存即在java堆中劃分一塊對象所需內存空間。分配方式一般有兩種:指針碰撞和空閒列表。(選用哪種方式由java堆是否規整決定,是否規整由所採用的垃圾回收器是否帶有壓縮整理功能決定)

指針碰撞:假設java堆中內存是絕對規整的,所有用過的內存都放在一邊,空閒的放在一邊,中間放着一個指針作爲分界點的指示器,分配內存僅僅是把指針向空閒空間移動一段與對象大小相等的距離。

空閒列表:如果java堆不是規整的,就必須維護一個列表記錄哪些內存塊可用,在分配時找出一塊足夠大的空間劃分給對象,並更新列表。

爲了保證在併發情況下分配內存,可以對分配內存空間的動作進行同步處理,也可以把內存分配動作按照線程劃分在不同空間之中進行,稱爲本地線程分配緩衝(TLAB)。內存分配完成後,虛擬機將分配到的內存空間都初始化爲零值(不包括對象頭),這一步保證了對象實例字段在java代碼中可以不賦初值就可以直接使用,然後虛擬機要對對象進行必要的設置,例如這個對象是哪個類的實例,如何找到類的元數據信息,對象的哈希碼,對象的GC分代年齡等,這些信息存放在對象的對象頭之中。一般來說,執行完new指令會接着執行<init>方法,把對象按照程序進行初始化。

b.對象的內存佈局(組成)

對象在內存存儲的佈局分爲3塊區域:對象頭,實例數據,對齊填充。

對象頭包括兩部分信息,第一部分用於存儲對象自身的運行數據,如哈希碼,鎖狀態標誌,線程持有的鎖等,這部分數據長度在32位和64位的虛擬機(未開啓壓縮指針)分別爲32bit,64bit。另一部分是類型指針,即對象指向它類元數據的指針,虛擬機通過這個指針確定對象是哪個類的實例。如果對象是數組,那在對象頭還必須有一塊用於記錄數組長度的數據。

實例數據是在代碼塊中所定義的各種類型的字段內容,無論從父類繼承下來的,還是子類中定義的,都需要記錄起來。這部分的存儲順序會受到虛擬機分配策略參數(FieldsAllocationStyle)和字段在java源碼中定義的順序的影響。

對齊填充並不是必然存在,也沒有特別地含義,僅僅起着佔位符的作用。

c.對象的訪問定位

使用對象時,我們的java程序需要通過棧上的reference數據來操作堆上的具體對象。由於reference類型在java虛擬機規範只規定了一個指向對象的引用,沒有定義具體方式,所以具體取決於虛擬機的實現。主流方式有使用句柄和直接指針。

句柄訪問:java堆中會劃分出一塊內存來作爲句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自的地址信息。

直接指針:java堆對象的佈局中就必須考慮如何放置訪問類型數據的相關信息,而reference中存儲的直接是對象地址。

兩種方式各有優勢,使用句柄最大好處是reference存儲的是穩定的句柄地址,在對象被移動時只會改變句柄中的實例數據指針。直接指針好處是節省了一次指針定位的時間,速度快。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章