一 對象在內存中的佈局
1.1對象的創建過程
對象的創建過程可以如下圖所示:
1.2 什麼是符號引用和直接引用,爲什麼需要在常量池定位到符號的引號?
在類的解析階段,把虛擬機常量池內的符號引號替換爲直接引用。
1.2.1 符號引用(SymbolicReferences)
就是用一組符號來描述所引用的目標,符號可以是任何形式的,只要使用時能夠定位到目標即可。
我們知道Java類編譯成class文件,編譯的時候,該類並不知道其所引用類的實際內存地址,因此只能使用符號引用來代替。
1.2.2 直接引用
直接飲用就是直接指向目標的指針,比如指向對象,或者類方法,類變量的指針,亦或者指向實例變量和實例方法的指針
二 對象內存分配方式
2.1 指針碰撞
指針碰撞就是指需要分配內存的時候,只需要將指針向空閒區域移動即可。所以堆內存空間空閒內存需要十分規整纔可以。
2.2 空閒列表
很多時候,正在使用的內存和未被使用的內存十分不規整,分佈很零散。虛擬機維護了一個空閒列表,記錄哪些內存塊可用,每次要分配內存時候就去空閒啊列表查詢,找到一個足夠的內存塊去分配。
我們比較這兩者分配方式,很明顯指針碰撞的分配效率高於空閒列表,但是前提是空閒內存規整。如果不規整的話,空閒列表是比較適合的,比指針碰撞節約內存。
三 內存分配線程安全問題
假設我們線程A請求分配內存,虛擬機從空閒列表查詢取出了一塊內存,分配給線程A的對象,這時候還沒來得及更新空閒列表,線程B也請求分配內存,也把這塊內存分配給了線程B,這就存在內存分配線程安全問題。
怎麼解決呢?
第一種方式:加鎖(影響性能)
第二種方式:TLAB (ThreadLocal Allocate Buffer)本地線程分配緩衝區
四 對象結構
# Header: 主要包括自身運行時數據;類型指針
# InstanceData
# Padding
4.1 對象頭(header)
對象頭用於存儲一些元數據,比如自身運行時數據和類型指針。
自身運行時數據(Mark Word)
# 哈希值
# GC分代年齡
# 鎖狀態標誌
# 線程持有的鎖
# 偏向線程ID和時間戳
它佔用內存大小和操作系統有關,如果是32位的操作系統,那麼它就是32位;如果是64位的操作系統,那麼他就是64位。如果數據遠遠大於32位,是如何進行存儲的呢?
類型指針
指向對象指向其類的元數據的指針,虛擬機通過指針來確定對象是哪一個類的實例。
4.2 實例數據(InstanceData)
4.3 對齊填充(Padding)
爲什麼需要填充?
對齊填充並不是必然的,也沒什麼特殊的含義,僅僅起着佔位符的作用。HotSpot虛擬機內存管理規定對象起始地址必須是8字節的整數倍,比如header部分正好都是8的整數倍,如果實例數據部分大小不夠8的整數倍,即沒有對齊,就需要使用Padding填充
五 對象的訪問定位
棧中的reference引用指向堆內存的地址並不一定就是對象本身。
5.1 使用句柄
並不是直接指向,而是指向堆中一塊區域,叫做句柄池。句柄池裏保存了實例對象的地址。
優點:棧中reference的引用地址不需要改變,改變的只是句柄池的對象實例地址
5.2 直接指針
直接指向堆中對象內存區域,HotSpot就是使用這種