1. 程式人生 > >對象內存結構(hotspot)

對象內存結構(hotspot)

可能 對象創建 方法 代碼 字段 代碼執行 就是 策略 分配內存

一.對象的整體結構

引用地址

技術分享圖片

1.對象頭

圖中可以看出對象頭分為MarkWord與Class對象指針,其中MarkWord標識了對象運行時的各種屬性與狀態值,哈希碼(HashCode).GC分代 年狀 態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等. 而Class對象指針則指向一個類在被類加載器讀入內存後生成 的Class對象的內存地址,這樣就可以通過對象判斷它是哪個類的實例 。。。class對象指針:這段空間是用來保存自己的父類通過類加載器加載到內存後生成的類的Class對象的地址,這樣就可以通過對象知道它是屬於哪個類的。

2.實例數據

實例數據是保存對象真正有效的數據,也就是對象的各種字段信息,其中也包括從父類中繼承的字段,都保存在這裏,當然方法不在這裏,在類中。而且它的內存具體分配結構是受jvm分配策略與字段在源碼中的順序來決定的,默認是相同寬度的字段分配在一起,在這個前提下父類字段在子類字段前面,而且子類中較窄的字段也可能被分配到父類中的間隙中。

3.對齊填充

因為hotspot虛擬機的內存管理系統要求內存的起始地址必須是8的整數倍,所以這段填充就是為了保證地址是8的整數倍。

二.對象的內存分配

就目前我所知道的內存分配方式有兩種,一種是指針碰撞,也就是假設Java的內存是完全規整的,分配了的內存在一邊,未分配的內存在另一半,每次分配完內存後,指針就往後移動。另一種就是空閑列表,也就是每分配一塊內存都會在一個列表裏進行記錄,這樣通過這個列表就可以知道那些內存是可以使用的,那些是已經分配了的。  所以具體使用哪種分配方式是由內存是否規整來決定的,而內存是否規整又是由垃圾回收器的具體算法來決定的,比如ParNew,Serial等采用指針碰撞,而CMS就是采用的空閑列表。

還有一個比較重要的問題就是,在多線程的情況下,創建對象分配內存是存在線程安全問題的,有可能這個線程還沒把一個對象完全創建完,就切換到另一個線程執行了並且使用了這個還沒完全創建完的對象,那就由問題了,所以對象創建必須是原子的,不可分的,那麽虛擬機是采用的CAS加上失敗重試來解決的,另一種辦法就是很幹脆的,直接給每個線程分配一些用來創建對象的內存空間,這樣每個線程中的對象就是獨立的了,不會被其他線程訪問到了,也就不存在線程安全了,每個線程都有程序計數器來保存當前代碼執行的位置,等到切換回來就根據程序計數器繼續執行就可以了。

三.對象的訪問機制

1.句柄訪問方式

在棧中保存的reference是一個句柄,通過這個句柄可以訪問句柄池中的一塊信息,這塊信息保存了這個對象的內存地址信息與它的類的地址信息

圖片引用地址

技術分享圖片

2.直接地址訪問方式

棧中的reference保存的就是這個對象的內存地址,通過這個地址就可以訪問到內存中的對象,在對象中在保存對象類型的信息

技術分享圖片

3.第一種方式的好處就是當內存的地址改變後,不需要改變棧中的refenence的句柄值,只要改變堆中句柄池的地址值就可以了,壞處就是訪問速度沒有第二種方式快,不過如果對象的內存地址頻繁改變,那麽就也需要頻繁的改變棧中的refrence中的地址值,就hotspot來說,它是采用的第二種方式。

四.實例

那麽一個int類型的對象占躲到的空間,一個int是4個字節,而int會自動裝箱為Integer對象,而對象由標識位,MarkWord,class指針,實例數據,對齊填充,MarkWord 4個字節,class指針4個字節,實例數據4個字節,這就是12個字節,而對象大小要是8的整數倍所以填充4個字節,總共16個字節

不過需要註意的是數組與普通對象不同,它還需要一個字段來保存自己長度值,所以如果是數組就還要加上一個int類型的length字段,也就是再多加四個字節。

對象內存結構(hotspot)