# jvm中的對象以及引用
問題
這篇文章主要探討的幾個問題:
-
jvm中對象創建過程
-
對象的內存佈局
-
對象的訪問方式
-
如何判斷對象是否存活
-
對象分配策略
-
四種引用的區別
jvm中對象的創建過程
- 檢查加載:檢查指令是否在一個常量池中定位到一個類的符號引用(一組符號描述所引用的目標),檢查類是否加載解析初始化
- 分配內存:從jvm的堆中劃分一塊內存,這個劃分有倆種情況,分別是指針碰撞和空閒列表
- 指針碰撞:如果jvm堆中的空間是規整的,空閒一邊,用過的一邊,中間放一個指針,那麼分配內存僅僅是吧指針挪動與對象大小等距距離;具體如圖
- 空閒列表:如果jvm堆種地空間不規整,那就需要維護一個空閒列表,分配時在空閒列表上找一塊足夠大的空間分配給對象,然後維護列表,具體如圖
- 分配內存的併發安全問題:在多線程情況下分配內存,創建對象,是一個極其頻繁的爲題,t1線程創建對象分配內存修改指針沒結束,t2就進來使用原指針分配內存,針對這個問題有倆種解決方式,一種是CAS,一種是分配緩衝TLAB
- CAS:通過cas操作,如果成功就分配內存,如圖
- TLAB:是一種基於線程本地緩存方式,通過cas分配內存,每次分配時現在自己的TLAB中找,如果沒有空間了就採用其他分配方式,比如直接分配;
- 分配緩衝:預先獲取一塊連續的內存,然後將緩衝池分成多塊,每次分配對象就從緩衝池中拿一塊進行分配;如果沒有空間了,則會使用其他分方式,比如直接分配;
- 初始化零值:被分配內存空間的對象都要初始化零值,int =0 boolean =false這一步的意義是保證java對象在不賦值初始化的情況下可以使用
- 設置對象頭:jvm對對象的必要設置,包括這個對象是哪個類的實例,如何找到類的元數據,對象hash ,gc,等信息,都在對象頭中;具體看下面
- 初始化對象:對於jvm來說,一個對象已經創建完了;
對象的內存佈局
- 在hotspot中對象的佈局分爲三類。分別是對象頭,實例數據,對齊填充,分別介紹一下
- 對象頭:存儲的是運行的數據它包含三部分,存儲對運行數據(markWord),類型指針,若是數組類型,有記錄數組長度的記錄
- 存儲對象自身運行時數據包含 haah,gc分代年齡,鎖標識,鎖類型,偏向線程id,偏向時間戳
- 實例數據
- 對齊填充
- 對象頭:存儲的是運行的數據它包含三部分,存儲對運行數據(markWord),類型指針,若是數組類型,有記錄數組長度的記錄
對象的訪問方式
- 句柄:如果採用句柄訪問,jvm堆中會劃分一塊區域做句柄池,reference(在對象頭中)中村的是句柄的地址,句柄中存的是對象實例和類型數據的地址
- 直接指針:reference中存的就是對象地址
- 對比:直接指針訪問塊,但是對象變更速度太快,所以一直修改開銷大,句柄要二次訪問慢,但是對面變更是句柄維護,reference中不需要修改。
判斷對象是否存活的方式
-
堆中存放着幾乎所有的對象,gc回收時要判斷這些對象是否存活,如何判斷存活,常見的有引用計數法,可達性分析
- 引用計數法:在對象中添加引用計數器,每當有一個地方引用就+1,失效時就-1,存在相互引用情況要引入額外基質處理,影響效率;
- 可達性分析:通過gc root作爲起點,從這個節點開始向下搜素,沿途路徑叫引用鏈,當一個對象到gc root沒有任何引用鏈,則證明這對象不可用;
- 常見的gc root如下
- 虛擬機棧中引用的對象
- 本地方法棧中引用的對象
- 方法區靜態屬性引用的對象
- jni引用的對象
- 虛擬機內部引用的對象,如常量池引用的對象
- jvm引用類型,軟弱虛引用
- 根集合中的對象,在gc前預設好的對象
- 常見的gc root如下
-
Finalize 方法 (沒啥用)
即使通過可達性分析判斷不可達的對象,也不是“非死不可”,它還會處於“緩刑”階段,真正要宣告一個對象死亡,需要經過兩次標記過程,一次是 沒有找到與 GCRoots 的引用鏈,它將被第一次標記。隨後進行一次篩選(如果對象覆蓋了 finalize),我們可以在 finalize 中去拯救。
四種對象引用以及區別
- 強: Object obj = new Object(); // 強引用
- 軟:SoftReference
- 弱: WeakReference
- 虛: WeakReference
- 他們之前的區別:那gc爲例,
- 強引用-obj存在就不會回收
- 軟引用-當內存不足時,會被回收
- 弱引用-下一次gc時就會被回收
- 虛引用-每逢gc必然回收,放入特殊隊列
對象分配策略
-
除了常見的分配在堆中,還會分配到棧中,既逃逸下面詳細說
-
對象的分配
-
棧上分配
- 首先說逃逸分析,分爲倆種,一種是線程逃逸,既被多個線程訪問的對象,另一種是方法逃逸,既被其他方法引用
- 如果對象沒有逃逸,則在棧上分配對象,這樣就會避免對象創建和回收;
- 舉個例子,myobject屬於不可逃逸,jvm在棧上分配
-
堆中分配:
-
對象優先在堆中的eden:大多數情況下對象在eden上分配,空間不夠就minor gc,這裏會涉及道空間分配擔保機制;
-
大對象直接進入老年代
-
長期存活的進入老年代:每一個對象有一個年齡計數器(在對象頭的運行時數據區中的gc年齡),達到一定閾值就會進入老年代
-
-