JAVA GC 垃圾回收
写一下欠了很久的一篇博客吧
对java的垃圾回收做一个学习的归纳总结
纯属个人学习理解,必然不够完善,欢迎在下面回复指出
那为什么要有垃圾回收呢
对象是建在哪里的呢? 对象用完了之后
如果不释放的话,一直在那里占用内存,是一种对资源的浪费
Mark-Sweep
Mark-Sweep-Compact
Mark-Copy
分代
-
Mark-Sweep:清除完垃圾未压缩内存,存在内存碎片
-
延时高:Old GC时会扫描清除整个Heap的垃圾。现在内存越来越大,延时也会随之增高
-
废弃:JDK9已经移除
-
软实时、低延时、高吞吐
-
可设定暂停目标
-
适用大内存、多处理器
-
Mark-Weep-Compact
-
JDK9默认,用于替代CMS
-
传统
-
Heap划分连续,分为年轻代、年老代、
永久代(JDK8为元空间) -
分代size固定
-
-
G1
-
将Heap划分为若干个相等的Region,大概2000多个Region
-
-XX:G1HeapRegionSize = n default 2048
-
-XX:G1HeapRegionSize 1~32M
-
-
Heap划分不连续,分为年轻代、年老代、
永久代(JDK为元空间)-
Eden、Survivor、Old(Humongous)
-
Humongous(超过Region大小的50%则直接在Old区分配)
-
-
-
G1 GC分为两个过程:Young GC、Mixed GC
-
Full GC使用Serial Old GC
目的
-
两个分区有引用关系,那么如何找到引用关系?
-
GC最早引入卡表的目的是为了对内存的引用关系做标记,从而根据引用关系快速遍历活跃对象
-
每个Card 覆盖一定范围的Heap(一般为512Bytes)。G1的RSet是在Card Table的基础上实现的
-
每个Region被分成了多个Card,在不同Region中的Card会相互引用
问题
-
Young GC、Mixed GC需要标记所有Region嘛?
-
Full GC才需要全部标记
-
-
Old Region对象可能持有Young Region对象引用?
-
不同Region可能会相互引用?
引用关系
-
分区内部有引用关系 N
-
无论Young或者Old都不需要,因为回收粒度是一个分区,会遍历整个分区
-
-
新生代分区到新生代分区之间有引用关系 N
-
无论哪一种GC,都会全量标记回收整个Young区域
-
-
新生代分区到老生代分区之间有引用关系 N
-
Young GC无需记录,Mixed GC会先Young GC,然后以Young分区作为根扫描
-
-
老生代分区到新生代分区之间有引用关系 Y
-
需要记录,Young GC时会以Old Region引用作为根扫描
-
-
老生代分区到老生代分区之间有引用关系 Y
-
Mixed GC时可能只会回收部分Old Region
-
定义
记忆集RSet是一种抽象概念,记录对象在不同Region之间的引用关系,目的是为了加速垃圾回收的速度。RSet结构其实是一个HashTable,Key是引用Region的起始地址,Value是一个集合,里面的元素是Card Table的Index
定义
Collection Set(CSet),它记录了GC要收集的Region集合,集合里的Region可以是任意年代的。
条件
当所有Eden Region被耗尽无法申请内存时,就会触发一次Young GC。
过程
-
STW?
-
选择要收集CSet,整个新生代分区就是CSet
-
并行任务处理
-
GC Roots扫描并处理
-
GC Roots直接引用的对象复制到新的Survivor区
-
-
更新RSet
-
借助Write Barrier存入到Dirty Card Queue
-
排空Dirty Card Queue
-
-
处理Old分区到Young分区的引用
-
把RSet所在卡表对应分区所有的对象都认为是根,把这些根引用的对象复制到新的Survivor区
-
-
Object Copy(GC Roots可达的对象),Mark-Sweep-Copy算法
-
释放CSet,把这些分区加入到自由列表(Free List)
-
调整Young Region数目(主要是根据GC执行时间和目标停顿时间预测下次可能发生GC能接受的最大数目)
-
调优
-
G1记录每个阶段的时间,⽤于⾃动调优
-
记录Eden/Survivor的数量和GC时间
-
根据暂停⽬标⾃动调整Region的数量
-
暂停⽬标越短,Eden数量越少,吞吐量越低
-
难点
并发标记的难点在于GC线程在标记对象过程中,应用线程可能正在改变对象引用关系,从而造成漏标和错标
定义
-
白色:未被标记
-
灰色:自身已被标记,但是拥有field未被标记
-
黑色:自身和拥有field都已标记
并发标记
-
问题
-
应用线程插入了一个从黑色对象到该白色对象的引用
-
应用线程删除了所有从灰对象到该白对象的直接或者间接引用
-
-
解决方案
-
对于第一个条件,如果该对象是new出来的,并没有被灰对象持有,会漏标嘛?
-
Region中有两个top-at-mark-start(TAMS)指针,分别为prevTAMS和nextTAMS。在TAMS以上的对象是新分配的,这是一种隐式的标记
-
Remark阶段仍然会被标记到的
-
-
对于GC已存在的对象,如果它还活着,必然会有一个灰对象直接或间接引用,如果该引用删除了或者被替换了,非常严重的错误?
-
SATB(Snapshot-At-The-Beginning),GC 开始时活着对象的一个快照
-
SATB破坏了第二个条件。一个对象的引用被替换时,可以通过write barrier将旧引用记录下来
-
造成浮动垃圾
-
-
过程
混合回收可以总结为两个阶段:
-
并发标记
-
条件:-XX:InitiatingHeapOccupancyPercent=N 45 by default(non_young_capacity_bytes)
-
初始标记子阶段
-
STW
-
piggybacked on a normal Young GC
-
根据Survivor分区作为根
-
-
并发标记子阶段
-
不暂停应用线程
-
根据Survivor分区以及Old区的RSet开始并发标记
-
每个GC线程每次只扫描一个分区(标记所有分区),从而标记存活对象,计算存活数量
-
-
重新标记子阶段
-
STW
-
从根(Survivor)出发,并发标记子阶段已经追踪了所有存活对象
-
所有的引用变更都被处理了;这里的引用变更包括新增空间分配和引用变更,新增的空间所有对象都认为是活的,引用变更处理SATB。
-
并行执行
-
-XX:ParallelGCThreads
-
-
-
清理子阶段
-
只清理没有存活对象的Region,不需要STW
-
-
-
Mixed GC
-
STW
-
G1HeapWastePercent=N,只有达到了才会触发Mixed GC(不一定立即发生)
-
选择若干个Region进行
-
-XX:G1MixedGCLiveThresholdPercent=N (default 85),存活对象占比,只有在此参数之下,才会选中到CSet中
-
根据暂停目标选择垃圾最多的Old Region优先进行----【G1名称由来】
-
-XX:G1MixedGCCountTarget=N,一次并发标记过后,最多执行Mixed GC的次数
-
-
Eden+Survivor Region+Old Region开始垃圾回收
-
-
条件
-
并发模式失效
-
Mixed GC之前,Old区就没剩余空间了,此时放弃标记
-
调整Heap大小
-
-
晋升失败
-
完成标记,没有足够空间供存活对象或晋升对象
-
-XX:InitiatingHeapOccupancyPercent 降低并发标记阈值
-
调整Heap大小
-
-XX:ConcGCThreads 增加并发标记线程数
-
-
疏散失败
-
Survivor空间和老年代中没有足够的空间容纳所有的幸存对象
-
-XX:InitiatingHeapOccupancyPercent 降低并发标记阈值
-
调整Heap大小
-
-XX:ConcGCThreads 增加并发标记线程数
-
Full GC是Serial Old GC,停顿时间很长,避免它触发
G1是一个响应时间优先的GC算法,用户可以设定整个GC过程的期望停顿时间,由参数MaxGCPauseMillis控制,默认值200ms。不过不是硬性条件,只是期望值。
衰减平均值和方差计算
重要参数
-
启用G1算法
-
-XX:+UseG1GC
-
-
GC暂停时间目标
-
-XX:MaxGCPauseMillis = 200 软目标
-
-
GC并发标记开始
-
-XX:InitiatingHeapOccupancyPercent = 45
-
最佳实践
-
不要设置年轻代大小
-
G1将不再遵守暂停时间目标。因此,实质上,设置年轻一代的大小会禁用暂停时间目标。
-
G1不再能够根据需要扩展和缩小年轻一代的空间。由于尺寸是固定的,因此无法更改尺寸
-
-
响应时间目标
-
90%或更多时间内能达到的目标值,不要设置过小,太小会频繁Young GC
-
-
避免疏散失败
-
增加Heap大小
-
增加-XX:G1ReservePercent=n,默认值为10
-
G1试图通过使备用内存空闲以防万一,希望有更多的“空间”
-
-
提前进行并发标记
-
-XX:InitiatingHeapOccupancyPercent 降低该阈值
-
-
使用该-XX:ConcGCThreads=n选项增加标记线程的数量。
-
ZGC
一款还未普遍使用的低延迟垃圾回收器,主要从美团技术抄写并理解
The Z Garbage Collector)是JDK 11中推出的一款低延迟垃圾回收器,它的设计目标包括:
- 停顿时间不超过10ms;
- 停顿时间不会随着堆的大小,或者活跃对象的大小而增加;
- 支持8MB~4TB级别的堆(未来支持16TB)。
参考链接