記憶體配置設定與回收政策
對象的記憶體配置設定,就是在堆上配置設定(也可能經過 JIT 編譯後被拆散為标量類型并間接在棧上配置設定),對象主要配置設定在新生代的 Eden 區上,如果開啟了本地線程配置設定緩沖,将按線程優先在TLAB上配置設定,少數情況下可能直接配置設定在老年代,配置設定規則不固定,取決于目前使用的垃圾收集器組合以及相關的參數配置。
1、對象優先在 Eden 配置設定
大多數情況下,對象在新生代 Eden 區中配置設定。當 Eden 區沒有足夠空間進行配置設定時,虛拟機将發起一次 Minor GC。
Minor GC vs Major GC/Full GC:
- Minor GC:回收新生代(包括 Eden 和 Survivor 區域),因為 Java 對象大多都具備朝生夕滅的特性,是以 Minor GC 非常頻繁,一般回收速度也比較快。
- Major GC / Full GC: 回收老年代,出現了 Major GC,經常會伴随至少一次的 Minor GC,但這并非絕對。Major GC 的速度一般會比 Minor GC 慢 10 倍 以上。
在 JVM 規範中,Major GC 和 Full GC 都沒有一個正式的定義,是以有人也簡單地認為 Major GC 清理老年代,而 Full GC 清理整個記憶體堆。
2、大對象直接進入老年代
大對象定義:是指需要大量連續記憶體空間的 Java 對象,如很長的字元串或資料。
虛拟機提供了一個 -XX:PretenureSizeThreshold 參數,令大于這個設定值的對象直接在老年代配置設定,機關是位元組;隻對 Serial 和 ParNew 兩款收集器有效。
為什麼大對象直接進入老年代?
- 一個大對象能夠存入 Eden 區的機率比較小,發生配置設定擔保的機率比較大,而配置設定擔保需要涉及大量的複制,就會造成效率低下
- 因為新生代采用的是複制算法收集垃圾,大對象直接進入老年代可以避免在 Eden 區和 Survivor 區發生大量的記憶體複制。
3、長期存活的對象将進入老年代
- 固定對象年齡判定: 虛拟機給每個對象定義一個年齡計數器,對象每在 Survivor 中熬過一次 Minor GC,年齡 +1,達到
設定值後,會被晉升到老年代,-XX:MaxTenuringThreshold
預設為 15;-XX:MaxTenuringThreshold
- 動态對象年齡判定: Survivor 中有相同年齡的對象的空間總和大于 Survivor 空間的一半,那麼,年齡大于或等于該年齡的對象直接晉升到老年代。
4、空間配置設定擔保
我們知道,新生代采用的是複制算法清理記憶體,每一次 Minor GC,虛拟機會将 Eden 區和其中一塊 Survivor 區的存活對象複制到另一塊 Survivor 區,但 當出現大量對象在一次 Minor GC 後仍然存活的情況時,Survivor 區可能容納不下這麼多對象,此時,就需要老年代進行配置設定擔保,即将 Survivor 無法容納的對象直接進入老年代。
這麼做有一個前提,就是老年代得裝得下這麼多對象。可是在一次 GC 操作前,虛拟機并不知道到底會有多少對象存活,是以空間配置設定擔保有這樣一個判斷流程:
- 發生 Minor GC 前,虛拟機先檢查老年代的最大可用連續空間是否大于新生代所有對象的總空間;
- 如果大于,Minor GC 一定是安全的;
- 如果小于,虛拟機會檢視 HandlePromotionFailure 參數,看看是否允許擔保失敗;
- 允許失敗:嘗試着進行一次 Minor GC;
- 不允許失敗:進行一次 Full GC;
- 不過 JDK 6 Update 24 後,HandlePromotionFailure 參數就沒有用了,規則變為隻要老年代的連續空間大于新生代對象總大小或者曆次晉升的平均大小就會進行 Minor GC,否則将進行 Full GC。通過清除老年代中廢棄資料來擴大老年代空閑空間,以便給新生代作擔保。
5、Metaspace 元空間與 PermGen 永久代
Java 8 徹底将永久代 (PermGen) 移除出了 HotSpot JVM,将其原有的資料遷移至 Java Heap 或 Metaspace。
移除 PermGen 的原因:
- PermGen 記憶體經常會溢出,引發惱人的 java.lang.OutOfMemoryError: PermGen,是以 JVM 的開發者希望這一塊記憶體可以更靈活地被管理,不要再經常出現這樣的 OOM;
- 移除 PermGen 可以促進 HotSpot JVM 與 JRockit VM 的融合,因為 JRockit 沒有永久代。
移除 PermGen 後,方法區和字元串常量的位置:
- 方法區:移至 Metaspace;
- 字元串常量:移至 Java Heap。
Metaspace 的位置: 本地堆記憶體(native heap)。
Metaspace 的優點: 永久代 OOM 問題将不複存在,因為預設的類的中繼資料配置設定隻受本地記憶體大小的限制,也就是說本地記憶體剩餘多少,理論上 Metaspace 就可以有多大;
JVM參數:
:配置設定給類中繼資料空間(以位元組計)的初始大小,為估計值。MetaspaceSize的值設定的過大會延長垃圾回收時間。垃圾回收過後,引起下一次垃圾回收的類中繼資料空間的大小可能會變大。
-XX:MetaspaceSize
:配置設定給類中繼資料空間的最大值,超過此值就會觸發Full GC,取決于系統記憶體的大小。JVM會動态地改變此值。
-XX:MaxMetaspaceSize
:一次GC以後,為了避免增加中繼資料空間的大小,空閑的類中繼資料的容量的最小比例,不夠就會導緻垃圾回收。
-XX:MinMetaspaceFreeRatio
:一次GC以後,為了避免增加中繼資料空間的大小,空閑的類中繼資料的容量的最大比例,不夠就會導緻垃圾回收。
-XX:MaxMetaspaceFreeRatio
6、有哪些情況可能會觸發 JVM 進行 Full GC
-
System.gc() 方法的調用
此方法的調用是建議 JVM 進行 Full GC,注意這隻是建議而非一定,但在很多情況下它會觸發 Full GC,進而增加 Full GC 的頻率。通常情況下我們隻需要讓虛拟機自己去管理記憶體即可,我們可以通過 -XX:+ DisableExplicitGC 來禁止調用 System.gc()。
-
老年代空間不足
老年代空間不足會觸發 Full GC操作,若進行該操作後空間依然不足,則會抛出如下錯誤:
java.lang.OutOfMemoryError: Java heap space
-
永久代空間不足
JVM 規範中運作時資料區域中的方法區,在 HotSpot 虛拟機中也稱為永久代(Permanet Generation),存放一些類資訊、常量、靜态變量等資料,當系統要加載的類、反射的類和調用的方法較多時,永久代可能會被占滿,會觸發 Full GC。如果經過 Full GC 仍然回收不了,那麼 JVM 會抛出如下錯誤資訊:
java.lang.OutOfMemoryError: PermGen space
-
CMS GC 時出現 promotion failed 和 concurrent mode failure
promotion failed,就是上文所說的擔保失敗,而 concurrent mode failure 是在執行 CMS GC 的過程中同時有對象要放入老年代,而此時老年代空間不足造成的。
- 統計得到的Minor GC晉升到舊生代的平均大小大于老年代的剩餘空間