1.將新對象預留在新生代
由於Full GC的成本遠高於Minor GC,因此盡可能將對象分配在新生代是一項明智的做法。雖然在大部分情況下,JVM會嘗試在eden區分配對象,但是由於空間緊張等問題,很可能不得不將部分年輕對象提前向老年代壓縮。
在JVM參數調優中,可以為應用程序分配一個合理的新生代空間,以避免新對象直接進入老年代的情況。因為新生代垃圾回收的速度高於老年代回收。因此,將年輕對象預留在新生代有利於提升整體的GC效率。
2.大對象進入老年代
雖然在大部分情況下,將對象分配在新生代是合理的。但是,對於大對象,這種做法是值得商榷的。大對象出現在新生代很可能擾亂新生代GC,並破壞新生代原有的對象結構。
因為嘗試在新生代分配大對象,很可能導致空間不足,為了有足夠的空間容納大對象,JVM不得不將新生代中的年輕對象挪到老年代。因為大對象占用空間多,所以可能需要移動大量的小的年輕對象進入老年代。因為新生代的對象往往存活時間比較短,所以會引起多次的Full GC造成程序卡頓。
基於以上的原因,可以直接將大對象分配到老年代,以保持新生代對象結構的完整性,提高GC的效率。
可以使用參數-XX:PretenureSizeThreshold設置大對象直接進入老年代的閾值。當對象的大小超過這個值,將直接在老年代分配。
3.設置對象進入老年代的年齡
在堆中,每個對象都有自己的年齡,一般情況下,年輕對象存放在新生區,年老對象存放在老年區。為了做到這一點,JVM會為每一個對象都維護一個年齡
如果對象在eden區,經過一次GC之后還存活,則被移動到survivor區中,對象年齡加1 。之后對象每經過一次GC之后依然存活,則年齡再加1 。當對象年齡到達閾值,就移入老年代,成為老年對象。
這個閾值的最大值可以通過參數-XX:MaxTenuringThreshold來設置,它的默認值為15.但這並不意味着新對象一定要達到這個年齡才能進入老年代。對象實際進入老年代的年齡是虛擬機在運行時根據內存使用情況動態計算的,這個參數指定的是閾值的最大值。即實際進入老年代的年齡等於動態計算所得的年齡與參數指定的中較小的那一個。
4.穩定與震盪的堆大小
一般來說,穩定的堆大小對垃圾回收是有利的。獲得一個穩定的堆大小的方法是使-Xms和-xmx的大小一致,即最大堆和最小堆都一樣。
如果這樣設置,系統在運行時,堆的大小是恆定的,穩定的堆大小可以減少GC的次數,因此很多 服務端應用都會將最大堆和最小堆設置為相同的數值。
但是一個不穩定的堆大小也並非一無是處。穩定的堆大小雖然減少了GC的次數,但是同時也增加了GC的時間。讓堆大小在一個區間中震盪,在系統不需要使用大內存時,壓縮堆空間,使GC應對一個較小的堆可以加快單次GC的速度。
基於這樣的考慮,JVM還提供了兩個參數用於壓縮和拓展堆空間:
- -XX:MinHeapFreeRatio : 設置堆空間的最小空閑比例,默認是40.當堆空間的空閑內存小於這個數值時,JVM會拓展堆空間。
- -XX:MinHeapFreeRatio : 設置堆空間的最大空閑比例,默認是70.當堆空間的空閑內存大於這個數值時,JVM會拓展空間。
當-XMS和-Xmx相等時,這兩個參數是無效的。