Java虛擬機-垃圾回收器

一、如何查看垃圾回收器

查看JVM的默認垃圾回收器,可以看出JDK8中UseParallelGC即Parallel Scavenge+Serial Old

java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=266359616 
-XX:MaxHeapSize=4261753856 
-XX:+PrintCommandLineFlags 
-XX:+UseCompressedClassPointers 
-XX:+UseCompressedOops 
-XX:-UseLargePagesIndividualAllocation 
-XX:+UseParallelGC
java version "1.8.0_211"
Java(TM) SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)

查看垃圾回收器詳細信息

java -XX:+PrintGCDetails -version
java version "1.8.0_211"
Java(TM) SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)
Heap
 PSYoungGen      total 76288K, used 3932K [0x000000076b500000, 0x0000000770a00000, 0x00000007c0000000)
  eden space 65536K, 6% used [0x000000076b500000,0x000000076b8d7240,0x000000076f500000)
  from space 10752K, 0% used [0x000000076ff80000,0x000000076ff80000,0x0000000770a00000)
  to   space 10752K, 0% used [0x000000076f500000,0x000000076f500000,0x000000076ff80000)
 ParOldGen       total 175104K, used 0K [0x00000006c1e00000, 0x00000006cc900000, 0x000000076b500000)
  object space 175104K, 0% used [0x00000006c1e00000,0x00000006c1e00000,0x00000006cc900000)
 Metaspace       used 2366K, capacity 4480K, committed 4480K, reserved 1056768K
  class space    used 255K, capacity 384K, committed 384K, reserved 1048576K

二、常見垃圾回收器介紹

1.Serial

使用場景主要爲客戶端。是最基本,發展最悠久的單線程垃圾收集器,是JDK1.3.1前HotSpot新生代收集的唯一選擇。分爲新生代和老年代兩個,分別採用了複製算法和標記-整理算法。

1)特點

  • 針對新生代;
  • 採用複製算法;
  • 單線程收集;
  • 進行垃圾收集時,必須暫停所有工作線程,直到完成,即會"Stop The World"。

2)應用場景

  • 依然是HotSpot在Client模式下默認的新生代收集器;
  • 簡單高效(與其他收集器的單線程相比);
  • 對於限定單核CPU的環境,沒有線程開銷,可以獲得最高的單線程收集效率;
  • 在用戶的桌面應用場景中,可用內存一般不大(幾十M至一兩百M),可以在較短時間內完成垃圾收集(幾十MS至一百多MS),只要不頻繁發生,可以接受。

3)使用方式

 "-XX:+UseSerialGC":添加該參數來顯式使用串行垃圾收集器

2.ParNew

ParNew垃圾收集器是Serial收集器的多線程版本。

1)特點

  • 除了多線程外,其餘的行爲、特點和Serial收集器一樣,如Serial收集器可用控制參數、收集算法、Stop The World、內存分配規則、回收策略等;
  • 兩個收集器共用了不少代碼。

2)應用場景

  • 在Server模式下,ParNew收集器是一個非常重要的收集器,因爲除Serial外,目前只有它能與CMS收集器配合工作;
  • 但在單個CPU環境中,不會比Serail收集器有更好的效果,因爲存在線程交互開銷。

3)使用方式

-XX:+UseConcMarkSweepGC:指定使用CMS後,會默認使用ParNew作爲新生代收集器;
-XX:+UseParNewGC:強制指定使用ParNew;
-XX:ParallelGCThreads:指定垃圾收集的線程數量,ParNew默認開啓的收集線程與CPU的數量相同;

3.Parallel

Parallel Scavenge 收集器是一個新生代收集器,採用複製算法,並且是多線程收集器;與ParNew最大的不同,它關注的是垃圾回收的吞吐量(Throughput)。

Parallel Old 收集器是Parallel Scavenge 收集器的老年代版本,使用多線程和“標記-整理”算法。

JDK7和8環境下,默認使用 Parallel Scavenge(新生代)+ Serial Old(老年代)。

1)特點

  • 在注重吞吐量以及CPU資源敏感的場合,可以優先考慮Parallel Scavenge 加 Parallel Old 收集器。
  • 吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間)

2)應用場景

  • 主要適合在後臺運算而不需要太多交互的任務:
  • 停頓時間越短就越適合需要與用戶交互的程序,良好的響應速度能提升用戶體驗,而高吞吐量則可以高效的利用CPU時間,儘快完成程序的運算任務。

3)使用方式

-XX:+UseParallelGC :使用Parallel作爲垃圾收集器;
-XX:MaxGCPauseMillis:控制最大垃圾收集停頓時間
-XX:GCTimeRatio:設置吞吐量大小

4.CMS

CMS,Concurrent Mark Sweep,同樣是老年代的收集器。它關注的是垃圾回收最短的停頓時間(低停頓),在老年代並不頻繁GC的場景下,是比較適用的。

1)整體流程

  • 初始標記(initial mark),單線程執行,需要“Stop The World”,但僅僅把GC Roots的直接關聯可達的對象給標記一下,由於直接關聯對象比較小,所以這裏的速度非常快。
  • 併發標記(concurrent mark),對於初始標記過程所標記的初始標記對象,進行併發追蹤標記,此時其他線程仍可以繼續工作。此處時間較長,但不停頓。
  • 重新標記(remark),在併發標記的過程中,由於可能還會產生新的垃圾,所以此時需要重新標記新產生的垃圾。此處執行並行標記,與用戶線程不併發,所以依然是“Stop The World”,時間比初始時間要長一點。
  • 併發清除(concurrent sweep),併發清除之前所標記的垃圾。其他用戶線程仍可以工作,不需要停頓。

2)特點

  • 由於最耗費時間的併發標記與併發清除階段都不需要暫停工作,所以整體的回收是低停頓的;
  • Mark Sweep算法會導致內存碎片比較多;
  • CMS的併發能力依賴於CPU資源,所以在CPU數少和CPU資源緊張的情況下,性能較差;
  • 併發清除階段,用戶線程依然在運行,所以依然會產生新的垃圾,此階段的垃圾並不會再本次GC中回收,而放到下次。所以GC不能等待內存耗盡的時候才進行GC,這樣的話會導致併發清除的時候,用戶線程可以了利用的空間不足。所以這裏會浪費一些內存空間給用戶線程預留。

3)應用場景

  • CMS只會回收老年代和永久帶(1.8開始爲元數據區,需要設置CMSClassUnloadingEnabled),不會收集年輕帶;
  • CMS是一種預處理垃圾回收器,它不能等到old內存用盡時回收,需要在內存用盡前,完成回收操作,否則會導致併發回收失敗;所以CMS垃圾回收器開始執行回收操作,有一個觸發閾值,默認是老年代或永久帶達到92%。

4)使用方式

-XX:+UseConcMarkSweepGC 激活CMS收集器
-XX:ConcGCThreads 設置CMS線程的數量
-XX:+UseCMSInitiatingOccupancyOnly 只根據老年代使用比例來決定是否進行CMS
-XX:CMSInitiatingOccupancyFraction 設置觸發CMS老年代回收的內存使用率佔比
-XX:+CMSParallelRemarkEnabled 並行運行最終標記階段,加快最終標記的速度
-XX:+UseCMSCompactAtFullCollection 每次觸發CMS Full GC的時候都整理一次碎片
-XX:CMSFullGCsBeforeCompaction=* 經過幾次CMS Full GC的時候整理一次碎片
-XX:+CMSClassUnloadingEnabled 讓CMS可以收集永久帶,默認不會收集
-XX:+CMSScavengeBeforeRemark 最終標記之前強制進行一個Minor GC
-XX:+ExplicitGCInvokesConcurrent 當調用System.gc()的時候,執行並行gc,只有在CMS或者G1下該參數纔有效

5.G1

G1,Garbage First,在JDK 1.7版本正式啓用,是當時最前沿的垃圾收集器。G1可以說是CMS的終極改進版,解決了CMS內存碎片、更多的內存空間登問題。雖然流程與CMS比較相似,但底層的原理已是完全不同。

JDK1.9 默認垃圾收集器G1。

1)特點

  • 高效益優先。G1會預測垃圾回收的停頓時間,原理是計算老年代對象的效益率,優先回收最大效益的對象。
  • 堆內存結構的不同。以前的收集器分代是劃分新生代、老年代、持久代等。G1則是把內存分爲多個大小相同的區域Region,每個Region擁有各自的分代屬性,但這些分代不需要連續。

下面是老式收集器和G1收集器的分代對比:

  • 這樣的分區可以有效避免內存碎片化問題。但會造成分代內存不連續,導致在GC搜索垃圾對象的時候需要全盤掃描找出引用內存所在。所以G1對於每個Region都維護一個Remembered Set,用於記錄對象引用的情況。當GC發生的時候根據Remembered Set的引用情況去搜索。

2)整體流程

  • 初始標記(initial mark),標記了從GC Root開始直接關聯可達的對象。STW(Stop the World)執行。
  • 併發標記(concurrent marking),併發標記初始標記的對象,此時用戶線程依然可以執行。
  • 最終標記(Remark),STW,標記再併發標記過程中產生的垃圾。
  • 篩選回收(Live Data Counting And Evacuation),評估標記垃圾,根據GC模式回收垃圾。STW執行。

3)應用場景

  • G1是一款面向服務端應用的垃圾收集器。
  • 大堆(6左右,>=)運行,低GC延遲(<=0.5s)要求,
  • GC頻繁;收集或者壓縮時間長;收集對象年齡差異大。

4)使用方式

-XX:+UseG1GC
不要設置年輕代大小 --Xmn;G1自動調整
XX:MaxGCPauseMillis:設置爲能夠滿足90%請求的時間值。

6.ZGC

在JDK 11當中,加入了實驗性質的ZGC。它的回收耗時平均不到2毫秒。它是一款低停頓高併發的收集器。ZGC幾乎在所有地方併發執行的,除了初始標記的是STW的。所以停頓時間幾乎就耗費在初始標記上,這部分的實際是非常少的。

1)特點

  • 着色指針Colored Pointer。ZGC利用指針的64位中的幾位表示Finalizable、Remapped、Marked1、Marked0(ZGC僅支持64位平臺),以標記該指向內存的存儲狀態。相當於在對象的指針上標註了對象的信息。在這個被指向的內存發生變化的時候(內存在Compact被移動時),顏色就會發生變化。
  • 讀屏障Load Barrier 。由於着色指針的存在,在程序運行時訪問對象的時候,可以輕易知道對象在內存的存儲狀態(通過指針訪問對象),若請求讀的內存在被着色了。那麼則會觸發讀屏障。讀屏障會更新指針再返回結果,此過程有一定的耗費,從而達到與用戶線程併發的效果。

2)整體流程

與標記對象的傳統算法相比,ZGC在指針上做標記,在訪問指針時加入Load Barrier(讀屏障),比如當對象正被GC移動,指針上的顏色就會不對,這個屏障就會先把指針更新爲有效地址再返回,也就是,永遠只有單個對象讀取時有概率被減速,而不存在爲了保持應用與GC一致而粗暴整體的Stop The World。

3)性能提升

ZGC的吞吐量與Parallel GC(優化吞吐量)大致相當,但平均暫停時間爲1ms,最長爲4ms。 與之相比G1和Parallel會有很多超過200ms的GC停頓。

三、補充

1.Stop-The-World

JVM在後臺自動發起和自動完成的,在用戶不可見的情況下,把用戶正常的工作線程全部停掉,即GC停頓,這會帶給用戶不良的體驗。從JDK1.3到現在,從Serial收集器-》Parallel收集器-》CMS-》G1,用戶線程停頓時間不斷縮短,但仍然無法完全消除。

2.爲什麼只有ParNew能與CMS收集器配合?

CMS是HotSpot在JDK1.5推出的第一款真正意義上的併發(Concurrent)收集器,第一次實現了讓垃圾收集線程與用戶線程(基本上)同時工作。CMS作爲老年代收集器,但卻無法與JDK1.4已經存在的新生代收集器Parallel Scavenge配合工作,因爲Parallel Scavenge(以及G1)都沒有使用傳統的GC收集器代碼框架,而另外獨立實現;而其餘幾種收集器則共用了部分的框架代碼。所以如果老年代使用了CMS,那新生代只能使用ParNew。

3.CMS垃圾回收中,既然Mark Sweep會造成內存碎片,那麼爲什麼不把算法換成Mark Compact呢?

答案其實很簡答,因爲當併發清除的時候,用Compact整理內存的話,原來的用戶線程使用的內存還怎麼用呢?要保證用戶線程能繼續執行,前提的它運行的資源不受影響嘛。Mark Compact更適合“Stop the World”這種場景下使用。

參考資料:

  1. https://blog.csdn.net/wxy941011/article/details/80653738
  2. https://www.cnblogs.com/datiangou/p/10245874.html
  3. https://blog.csdn.net/mc90716_163/article/details/84919902?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
  4. https://www.cnblogs.com/cuizhiquan/articles/10936986.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章