Java虛擬機JVM之內存分配與回收策略

內存分配與回收策略

對象的內存分配,就是在堆上分配(也可能經過 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 設定值後,會被晉升到老年代,-XX:MaxTenuringThreshold 默認爲 15;
  • 動態對象年齡判定: 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參數:

  • -XX:MetaspaceSize:分配給類元數據空間(以字節計)的初始大小,爲估計值。MetaspaceSize的值設置的過大會延長垃圾回收時間。垃圾回收過後,引起下一次垃圾回收的類元數據空間的大小可能會變大。
  • -XX:MaxMetaspaceSize:分配給類元數據空間的最大值,超過此值就會觸發Full GC,取決於系統內存的大小。JVM會動態地改變此值。
  • -XX:MinMetaspaceFreeRatio:一次GC以後,爲了避免增加元數據空間的大小,空閒的類元數據的容量的最小比例,不夠就會導致垃圾回收。
  • -XX:MaxMetaspaceFreeRatio:一次GC以後,爲了避免增加元數據空間的大小,空閒的類元數據的容量的最大比例,不夠就會導致垃圾回收。

 

6、有哪些情況可能會觸發 JVM 進行 Full GC

  1. System.gc() 方法的調用
    此方法的調用是建議 JVM 進行 Full GC,注意這只是建議而非一定,但在很多情況下它會觸發 Full GC,從而增加 Full GC 的頻率。通常情況下我們只需要讓虛擬機自己去管理內存即可,我們可以通過 -XX:+ DisableExplicitGC 來禁止調用 System.gc()。

  2. 老年代空間不足
    老年代空間不足會觸發 Full GC操作,若進行該操作後空間依然不足,則會拋出如下錯誤:
    java.lang.OutOfMemoryError: Java heap space

  3. 永久代空間不足
    JVM 規範中運行時數據區域中的方法區,在 HotSpot 虛擬機中也稱爲永久代(Permanet Generation),存放一些類信息、常量、靜態變量等數據,當系統要加載的類、反射的類和調用的方法較多時,永久代可能會被佔滿,會觸發 Full GC。如果經過 Full GC 仍然回收不了,那麼 JVM 會拋出如下錯誤信息:
    java.lang.OutOfMemoryError: PermGen space

  4. CMS GC 時出現 promotion failed 和 concurrent mode failure
    promotion failed,就是上文所說的擔保失敗,而 concurrent mode failure 是在執行 CMS GC 的過程中同時有對象要放入老年代,而此時老年代空間不足造成的。

  5. 統計得到的Minor GC晉升到舊生代的平均大小大於老年代的剩餘空間

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章