Java對象分配簡要流程

jvm系列算法

本文主要簡述Java對象在內存中的分配過程

整體流程

clipboard.png

分配流程

clipboard.png

逃逸分析

逃逸分析的基本行爲就是分析對象動態做用域:當一個對象在方法中被定義後,它可能被外部方法所引用。

  • 方法逃逸:例如做爲調用參數傳遞到其餘方法中。

  • 線程逃逸:有可能被外部線程訪問到,譬如賦值給類變量或能夠在其餘線程中訪問的實例變量。

棧上分配(Stack Allocation)

Java堆中的對象對於各個線程都是共享和可見的,只要持有這個對象的引用,就能夠訪問堆中存儲的對象數據。虛擬機的垃圾收集系統能夠回收堆中再也不使用的對象,但回收動做不管是篩選可回收對象,仍是回收和整理內存都須要耗費時間。

若是肯定一個對象不會逃逸出方法以外,那讓這個對象在棧上分配內存將會是一個很不錯的主意,對象所佔用的內存空間就能夠隨棧幀出棧而銷燬。在通常應用中,不會逃逸的局部對象所佔的比例很大,若是能使用棧上分配,那大量的對象就會隨着方法的結束而自動銷燬了,垃圾收集系統的壓力將會小不少。

在實際的應用程序,尤爲是大型程序中反而發現實施逃逸分析可能出現效果不穩定的狀況,或因分析過程耗時但卻沒法有效判別出非逃逸對象而致使性能(即時編譯的收益)有所降低,因此在很長的一段時間裏,即便是Server Compiler,也默認不開啓逃逸分析,甚至在某些版本(如JDK 1.6 Update18)中還曾經短暫地徹底禁止了這項優化。

對象內存分配的兩種方法

爲對象分配空間的任務等同於把一塊肯定大小的內存從Java堆中劃分出來。

  • 指針碰撞(Serial、ParNew等帶Compact過程的收集器)
    假設Java堆中內存是絕對規整的,全部用過的內存都放在一邊,空閒的內存放在另外一邊,中間放着一個指針做爲分界點的指示器,那所分配內存就僅僅是把那個指針向空閒空間那邊挪動一段與對象大小相等的距離,這種分配方式稱爲「指針碰撞」(Bump the Pointer)。

  • 空閒列表(CMS這種基於Mark-Sweep算法的收集器)
    若是Java堆中的內存並非規整的,已使用的內存和空閒的內存相互交錯,那就沒有辦法簡單地進行指針碰撞了,虛擬機就必須維護一個列表,記錄上哪些內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的記錄,這種分配方式稱爲「空閒列表」(Free List)。

選擇哪一種分配方式由Java堆是否規整決定,而Java堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定。所以,在使用Serial、ParNew等帶Compact過程的收集器時,系統採用的分配算法是指針碰撞,而使用CMS這種基於Mark-Sweep算法的收集器時,一般採用空閒列表。

TLAB分配

對象建立在虛擬機中是很是頻繁的行爲,即便是僅僅修改一個指針所指向的位置,在併發狀況下也並非線程安全的,可能出現正在給對象A分配內存,指針還沒來得及修改,對象B又同時使用了原來的指針來分配內存的狀況。

解決這個問題有兩種方案,一種是對分配內存空間的動做進行同步處理——實際上虛擬機採用CAS配上失敗重試的方式保證更新操做的原子性;另外一種是把內存分配的動做按照線程劃分在不一樣的空間之中進行,即每一個線程在Java堆中預先分配一小塊內存,稱爲本地線程分配緩衝(Thread Local Allocation Buffer, TLAB)。

哪一個線程要分配內存,就在哪一個線程的TLAB上分配,只有TLAB用完並分配新的TLAB時,才須要同步鎖定。虛擬機是否使用TLAB,能夠經過-XX:+/-UseTLAB參數來設定。一般默認的TLAB區域大小是Eden區域的1%,固然也能夠手工進行調整,對應的JVM參數是-XX:TLABWasteTargetPercent。內存分配完成後,虛擬機須要將分配到的內存空間都初始化爲零值(不包括對象頭),若是使用TLAB,這一工做過程也能夠提早至TLAB分配時進行。這一步操做保證了對象的實例字段在Java代碼中能夠不賦初始值就直接使用,程序能訪問到這些字段的數據類型所對應的零值。

從內存分配的角度來看,線程共享的Java堆中可能劃分出多個線程私有的分配緩衝區(Thread Local Allocation Buffer,TLAB)。不過不管如何劃分,都與存放內容無關,不管哪一個區域,存儲的都仍然是對象實例,進一步劃分的目的是爲了更好地回收內存,或者更快地分配內存。

相關參數

-XX:PretenureSizeThreshold設置大對象直接進入年老代的閾值,當對象大小超過這個值時,將直接在年老代分配。

-XX:MaxTenuringThreshold是給Serial收集器和沒有開啓UseAdaptiveSizePolicy的ParNew GC收集器用的(在計算存活週期這個閾值時,hotspot會遍歷全部age的table,並對其所佔用的大小進行累積,當累積的大小超過了survivor space的一半時,則以這個age做爲新的存活週期閾值,最後取age和MaxTenuringThreshold中更小的一個值。),
對於PSGC而言,第一次以InitialTenuringThreshold(默認值爲7)來算,以後的minorGC會動態估算(另外,也會根據TargetSurvivorRatio來動態計算年齡)

回顧

對象在內存的引用方式

clipboard.png

class文件中method的結構

clipboard.png

對象在內存的結構

clipboard.png

參考

相關文章
相關標籤/搜索