天天看點

【JVM】說說java中的堆區

堆(Heap)是被虛拟機所管理的最大的一塊記憶體區域,在堆中,會有以下一些對象:

  • 朝生夕死的小對象,蜉蝣一般
  • 大對象,例如長數組,需要大量連續的記憶體空間
  • 長周期對象,存活很久,很能熬

是以,目前主流的JVM,利用可達性分析算法分析對象是否死亡,最後針對性地采用分代搜集算法回收死亡對象。判斷對象是否死亡,可以先參考我的另外一篇文章【JAVA】如何判斷對象已經死亡?

堆可以分為新生代與老年代,用來存放不同類型的對象。如下圖所示:

【JVM】說說java中的堆區

新生代

新生代含有3個區域,1個Eden區,2個Survivor區,可以分别稱為from以及to區,from與to的大小相同。它們占用的大小比例預設為Eden:from:to=8:1:1,當然也可以通過參數-XX:SurvivorRatio來自定義比例。

新建立的對象都會處在Eden區,大對象會直接進入老年代,可以使用參數-XX:+PretenuerSizeThreshold來指定多大的對象。

新建立的對象優先在Eden區上配置設定,Eden區滿了之後,會觸發一次Minor GC,虛拟機會采用複制算法(不會産生記憶體碎片)先進行釋放記憶體,回收死亡對象,然後将存活的對象一次性複制進from區域中。若from區域不夠,則使用分派擔保機制将部分對象直接推入老年代。老年代空間不夠,将觸發一次Full GC。

發生過一次Minor GC之後,Eden區域基本空閑,新建立的對象依然在這塊區域上配置設定,當Eden區域又滿了之後,同樣還是出發第二次Minor GC,這次GC将會回收Eden與from區域,将存活的對象一次性地複制進to區域中。

這些對象在新生代中每熬過一次Minor GC,對象的年齡就會加1,當年齡達到某一個門檻值時(預設為15),将會被直接移到老年代中,可以使用參數-XX:MaxTenuringThreshold來指定這個門檻值。

因為新生代區域上對象,建立頻繁,死亡也頻繁,是以Minor GC也會變得十分頻繁,但是這種GC方式效率高,持續時間短。

老年代

老年代中主要有老年對象(超過年齡門檻值的對象)以及大對象。當老年代空間不足時,會觸發一次Full GC,由于各個垃圾收集器的實作不同或處于效率考慮,可能會采用标記清除算法,也可能采用标記整理的算法回收死亡對象。

當然,也不是對象必須達到年齡門檻值才會進入老年代中,如果Survivor内某個相同年齡下(比如10歲)的所有對象的大小總和超過Survivor區的一半,那麼年齡≥10歲的對象将直接進入老年代中。

Full GC不像Minor GC那麼頻繁,但持續時間長。倘若頻繁發生Full GC,會嚴重阻礙應用程式的執行。

也有一些部落格會将Full GC與Major GC區分開來,認為他們的差別是Full GC會對整個堆進行回收,而Major GC隻會對老年代進行回收。但其實作在已經不區分這兩者了,很多性能監控工具也不再區分,現在大可認為Full GC與Major GC沒有差别。

常用參數

(1)新生代初始大小:-XX:NewSize,最大:-Xmn(-XX:MaxNewSize)

(2)Eden區與1個Survivor區的比例:-XX:SurvivorRatio

如果新生代的大小為10M,新生代=1個Eden+2個Survivor,并且設定-XX:SurvivorRatio=8,那麼此時Eden區占用8M,每個Survivor區各占1M。

(3)老年代初始大小:-XX:OldSize

(4)老年代與新生代的比例:-XX:NewRatio

如果設定堆總大小為100M,堆=新生代+老年代,并且設定-XX:NewRatio=4,那麼老年代大小是新生代的4倍,此時新生代占20M,老年代占80M。

(5)列印GC資訊:-XX:+PrintGCDetails

(6)在GC前後列印堆資訊:-XX:+PrintHeapAtGC

(7)設定進入老年代的年齡門檻值:-XX:MaxTenuringThreshold

(8)設定直接進入老年代的大對象的大小門檻值:-XX:+PretenuerSizeThreshold

Minor GC的觸發條件

(1)Eden區空間不足,或者說Eden區放不下新建立的小對象。

Full GC觸發條件

(1)老年代空間不足,此時執行Full GC後,老年代空間依舊不足的話,虛拟機會抛出 java.lang.OutOfMemoryError: Java heap space異常

(2)調用System.gc()方法,這隻是通知或者是建議虛拟機進行Full GC,虛拟機可以根據情況選擇是否執行。