JVM堆記憶體詳解

素小暖發表於2020-11-01

一、簡介

JAVA堆記憶體管理是影響效能主要因素之一。
堆記憶體溢位是JAVA專案非常常見的故障,在解決該問題之前,必須先了解下JAVA堆記憶體是怎麼工作的。

先看下JAVA堆記憶體是如何劃分的,如圖:

  1. JVM記憶體劃分為堆記憶體和非堆記憶體,堆記憶體分為年輕代(Young Generation)、老年代(Old Generation),非堆記憶體就一個永久代(Permanent Generation)。
  2. 年輕代又分為Eden和Survivor區。Survivor區由FromSpace和ToSpace組成。Eden區佔大容量,Survivor兩個區佔小容量,預設比例是8:1:1。
  3. 堆記憶體用途:存放的是物件,垃圾收集器就是收集這些物件,然後根據GC演算法回收。
  4. 非堆記憶體用途:永久代,也稱為方法區,儲存程式執行時長期存活的物件,比如類的後設資料、方法、常量、屬性等。

在JDK1.8版本廢棄了永久代,替代的是元空間(MetaSpace),元空間與永久代上類似,都是方法區的實現,他們最大區別是:元空間並不在JVM中,而是使用本地記憶體。

元空間有注意有兩個引數:

  • MetaspaceSize :初始化元空間大小,控制發生GC閾值
  • MaxMetaspaceSize : 限制元空間大小上限,防止異常佔用過多實體記憶體

二、為什麼移除永久代?

移除永久代原因:為融合HotSpot JVM與JRockit VM(新JVM技術)而做出的改變,因為JRockit沒有永久代。
有了元空間就不再會出現永久代OOM問題了!

三、分代概念

新生成的物件首先放到年輕代Eden區,當Eden空間滿了,觸發Minor GC,存活下來的物件移動到Survivor0區,Survivor0區滿後觸發執行Minor GC,Survivor0區存活物件移動到Suvivor1區,這樣保證了一段時間內總有一個survivor區為空。經過多次Minor GC仍然存活的物件移動到老年代。
老年代儲存長期存活的物件,佔滿時會觸發Major GC=Full GC,GC期間會停止所有執行緒等待GC完成,所以對響應要求高的應用盡量減少發生Major GC,避免響應超時。
Minor GC : 清理年輕代
Major GC : 清理老年代
Full GC : 清理整個堆空間,包括年輕代和永久代
所有GC都會停止應用所有執行緒。

四、為什麼分代?

將物件根據存活概率進行分類,對存活時間長的物件,放到固定區,從而減少掃描垃圾時間及GC頻率。針對分類進行不同的垃圾回收演算法,對演算法揚長避短。

五、為什麼survivor分為兩塊相等大小的倖存空間?

主要為了解決碎片化。如果記憶體碎片化嚴重,也就是兩個物件佔用不連續的記憶體,已有的連續記憶體不夠新物件存放,就會觸發GC。

六、JVM堆記憶體常用引數

引數描述
-Xms堆記憶體初始大小,單位m、g
-Xmx(MaxHeapSize)堆記憶體最大允許大小,一般不要大於實體記憶體的80%
-XX:PermSize非堆記憶體初始大小,一般應用設定初始化200m,最大1024m就夠了
-XX:MaxPermSize非堆記憶體最大允許大小
-XX:NewSize(-Xns)年輕代記憶體初始大小
-XX:MaxNewSize(-Xmn)年輕代記憶體最大允許大小,也可以縮寫
-XX:SurvivorRatio=8年輕代中Eden區與Survivor區的容量比例值,預設為8,即8:1
-Xss

堆疊記憶體大小

 七、垃圾回收演算法(GC,Garbage Collection)

紅色是標記的非活動物件,綠色是活動物件。

1、標記-清除(Mark-Sweep)

GC分為兩個階段,標記和清除。首先標記所有可回收的物件,在標記完成後統一回收所有被標記的物件。同時會產生不連續的記憶體碎片。碎片過多會導致以後程式執行時需要分配較大物件時,無法找到足夠的連續記憶體,而不得已再次觸發GC。

2、複製(Copy)

將記憶體按容量劃分為兩塊,每次只使用其中一塊。當這一塊記憶體用完了,就將存活的物件複製到另一塊上,然後再把已使用的記憶體空間一次清理掉。這樣使得每次都是對半個記憶體區回收,也不用考慮記憶體碎片問題,簡單高效。缺點需要兩倍的記憶體空間。

3、標記-整理(Mark-Compact)

也分為兩個階段,首先標記可回收的物件,再將存活的物件都向一端移動,然後清理掉邊界以外的記憶體。此方法避免標記-清除演算法的碎片問題,同時也避免了複製演算法的空間問題。
一般年輕代中執行GC後,會有少量的物件存活,就會選用複製演算法,只要付出少量的存活物件複製成本就可以完成收集。而老年代中因為物件存活率高,沒有額外過多記憶體空間分配,就需要使用標記-清理或者標記-整理演算法來進行回收。

八、垃圾收集器

  • 序列收集器(Serial)
    比較老的收集器,單執行緒。收集時,必須暫停應用的工作執行緒,直到收集結束。
  • 並行收集器(Parallel)
    多條垃圾收集執行緒並行工作,在多核CPU下效率更高,應用執行緒仍然處於等待狀態。
  • CMS收集器(Concurrent Mark Sweep)

CMS收集器是縮短暫停應用實踐為目標而設計的,是基於標記清楚演算法實現,整個過程分為4個步驟:

  • 初始標記
  • 併發標記
  • 重新標記
  • 併發標記

其中,初始標記、重新標記這兩個步驟仍然需要暫停應用執行緒、初始標記只是標記一下GC Roots能直接關聯到的物件,速度很快,併發標記階段是標記可回收物件,而重新標記階段則是為了修正併發標記期間因使用者程式繼續執行導致標記成聖變動的那一部分物件的標記記錄,這個階段暫停時間比標記階段稍長一點,但遠比並發標記的時間短。

由於整個過程消耗最長的併發標記和併發清楚過程收集器執行緒都可以與使用者執行緒一起工作,所以,CMS收集器記憶體回收與使用者一起併發執行嗎,大大減少了暫停時間。

暫時學習到這裡,不想複製贊貼

轉載自:https://blog.51cto.com/lizhenliang/2164876?wx=

 

上一篇:《深入理解Java虛擬機器 1》Java記憶體區域與記憶體分配策略

下一篇:《深入理解Java虛擬機器 2》效能監控與調優

 

 

相關文章