準備知識:
存活性分析(怎麼找到垃圾):
1.Reference Count引用計數( jvm 沒用,python 裏使用了)
對象被引用,計數器加一,如果數值減到0,就判斷這個對象是垃圾,可以被回收。Objective-C使用這種方式
圖1-1
2.Root Searching根可達算法(主流)
像(圖1-2)中A,B,C相互引用的情況,按引用計數都是1,不是垃圾,不會被回收。
但沒有其他對象引用這3個對象,在java中是可被回收的垃圾
圖1-2
根可達算法:簡單地說,如果可以從任何一個已經定義的變量開始,直接或者通過其他對象的引用來訪問到某個對象,則該對象是可達的
圖1-3
根對象:
線程變量:從一個main方法運行開始,main線程棧中調用到的其他方法,main線程棧中訪問到的對象
靜態變量:方法區中的類靜態屬性引用的對象
常量池:方法區中的常量引用的對象
JNI指針:本地方法棧中JNI(Native方法)的引用的對象
主流編程語言均使用根可達算法
一、常用的垃圾回收算法
一)Mark-Sweep(標記清除算法)
圖2-1
標記階段:通過根可達算法,將沿途的對象進行標記,標記爲存活對象,其他未被標記的對象就是可回收對象。
清除階段:將全部對象掃描一遍,沒有標記的對象都進行清除。
特點:
算法相對簡單,在存活對象比較多的情況下,效率比較高
掃描2遍,效率偏低
容易產生碎片
標記清除算法不適合Eden區
二)Copying(拷貝算法)
圖2-2
拷貝算法:準備一塊新空間,將存活對象都複製到新空間中。然後將舊空間中的對象全部回收。
特點:
適合存活對象較少的情況,適合Eden區
相對於複製清除算法,節省了存在大量對象時重新掃描一遍內存的開銷,且不會產生碎片
但是,增加了內存消耗,實際內存使用率僅50%
HotSpot虛擬機的Serial、ParNew等新生代收集器均採用半區複製分代策略
三)Mark-Compact(標記壓縮)
圖2-3
標記階段(與標記清除算法相同):過根可達算法,將沿途的對象進行標記,標記爲存活對象,其他未被標記的對象就是可回收對象。
壓縮階段:
- 遍歷堆, 將所有對象通過計算得到新的地址並保存
- 遍歷堆, 將所有子對象的地址更新爲新的地址, 同時更新根集合中的指針.
- 遍歷堆, 將對象集體遷移. 指針的問題都解決了, 可以將對象搬到新家了.
常用實現算法:Lisp2算法、Two-Finger算法
特點:
不會產生碎片,不會產生內存減半的問題
掃描2次,需要移動對象,效率偏低
二、垃圾回收器的分代
ZGC之前的算法存在分代,ZGC及以後的算法不再進行分代
垃圾回收器分代,分爲邏輯分代和物理分代
其中,G1僅在邏輯分代,物理不分代
物理分代
區分爲:Young(新生代) Old(老年代) 其中新生代:老年代=1:2 (JDK1.8)
其他分代
新生代 + 老年代 + 永久代(1.7)Perm Generation/ 元數據區(1.8) Metaspace
1. 永久代 元數據 - Class
2. 永久代必須指定大小限制 ,元數據可以設置,也可以不設置,無上限(受限於物理內存)
3. 字符串常量 1.7 - 永久代,1.8 - 堆
4. MethodArea邏輯概念 - 永久代、元數據
對象何時進入老年代
年齡(age):
age,官方文檔默認爲15(CMS特殊只有6),age也相當於計數器,每次GC,age+1,age代表GC的次數。
超過指定的年齡(YGC),對象就會進入到老年代
對象分配過程
新創建的對象都會被分配到Eden區(一些大對象特殊處理,直接分配到老年代),這些對象經過第一次Minor GC(YGC)後,如果仍然存活,將會被移到Survivor區。對象在Survivor區中每熬過一次Minor GC,年齡就會增加1歲,當它的年齡增加到一定程度時,就會被移動到年老代中
際上大多就是老年代)進行分配擔保(Handle Promotion)
按垃圾回收發生的範圍
- 新生代收集Minor GC/Young GC:指目標只是新生代的垃圾收集。
- 老年代收集Major GC/Old GC:指目標只是老年代的垃圾收集。
- 整堆收集(Full GC):收集整個Java堆和方法區的垃圾收集。
三、常用的垃圾回收器
圖4-1
圖4-2
- Serial 年輕代 串行回收
- PS 年輕代 並行回收(jdk1.8默認)
- ParNew 年輕代 配合CMS的並行回收
- SerialOld
- ParallelOld
Serial:當工作時所有工作線程都停止,當工作的時候斷開的線程則是垃圾,如果突然加入Serial,則停止,進行垃圾清理
safe point: 線程停止
Serial Old:用在老年代,使用標記壓縮的算法,用的也是單線程
Parallel Scavenge:jdk1.8默認垃圾回收器
Parallel Old:標記壓縮算法
ParNew: Parallel New,在Parallel Scavenge的基礎上做了一些增強,以便可以配合CMS使用
CMS:
圖4-3
- 初始標記:標記根節點
- 併發標記:工作線程和標記同事進行(CMS耗時最長的階段)
- 重新標記:併發標記過程中重新產生的垃圾,重新標記一次
- 併發清理:併發清理階段會產生浮動垃圾
ConcurrentMarkSweep 老年代 併發的, 垃圾回收和應用程序同時運行,降低STW的時間(200ms)
CMS問題比較多,所以現在沒有一個版本默認是CMS,只能手工指定
CMS既然是MarkSweep,
- 就一定會有碎片化的問題,碎片到達一定程度,
- CMS的老年代分配對象分配不下的時候,使用SerialOld 進行老年代回收
併發標記的算法
CMS使用:三色標記算法+incremental Update算法
G1:三色標記算法+SATB算法,主要配合他的Rset來進行
ZGC:用的是顏色指針
本文來自學習 馬士兵老師的教程《JVM調優必備理論知識-GCCollector》 課件:https://gitee.com/kusebingtang/msb_jvm/blob/master/05_GC%20and%20Tuning.md
參考其他學員的筆記:https://blog.csdn.net/bch1991/article/details/109087237