找到垃圾的方法
1、引用计数:无法解决循环引用的问题
2、根可达性算法:CG root 包括 线程栈变量、静态变量、常量池、JNI指针(调用C或C++用到的本地方法对象)
清除算法
1、标记-清除 Mark - sweep 适合存活对象较多的。容易产生碎片,扫描两遍
2、拷贝 copy 适合存活对象较少的。浪费内存,移动复制对象,需要调整引用,扫描一遍
3、标记-压缩 Mark - compact 不容易产生碎片、不浪费内存,但 扫描两遍,移动对象
分代模型
除Epsilon ZGC Shenandoah之外的GC都是使用逻辑分代模型
G1是逻辑分代,物理不分代
除此之外不仅逻辑分代,而且物理分代
栈上分配
线程私有小对象
无逃逸
线程本地分配(TLAB)
本地线程大小占用Eden的1%
小对象
多线程的时候不用竞争Eden就可以申请空间
常见的垃圾回收器
1、serial 和 serial old
2、parallel 和 parallel old
3、parNew 和 CMS
4、G1
5、ZGC 和 Shenandoah
CMS (并发)
初始标记(只标根节点) -> 并发标记 ->重新标记(标记并发标记产生的新对象)-> 并发清理
CMS的问题
1、内存碎片,碎片太多,会用Serial Old 去清理老年代碎片
2、浮动垃圾(第二次回收)
并发标记算法:三色标记
垃圾收集器跟内存大小的关系
- Serial 几十兆
- PS 上百兆 - 几个G
- CMS - 20G
- G1 - 上百G
- ZGC - 4T - 16T(JDK13)
算法
CMS四个阶段
1、初始标记
2、并发标记
3、重新标记(并发标记阶段可能垃圾对象变成非垃圾对象)
4、并发清理
G1特点
- 并发收集
- 压缩空闲空间不会延长GC的暂停时间
- 更容易预测GC暂停的时间
- 适用不需要实现很高的吞吐量的场景,但响应很快
- CSet(collection Set)存储一组可被回收的分区集合
- RSet(RememberdSet)记录着两个不同的region相互引用的信息,每个region里都有一个RSet保存着被其他region里对象引用的信息
- 由于RSet的存在,每次给对象赋引用的时候,需要维护RSet,在维护的过程被称为GC中的写屏障,GC中的写屏障不等于内存屏障
- G1新老年代是动态的,不要手动知道,这是G1预测停顿时间的基础
G1有FGC?
- G1有FGC,当对象分配特别快,GC线程回收不完
- G1的FGC在java 10 以前是串行的,之后是并行的
如果G1产生FGC,应该怎么办?
扩内存
提高CPU性能
降低mixedGC触发的阈值,让mixedGC提早发生(默认45%)
MixedGC就是一套完整的CMS
三色标记(CMS和G1)
三色就是三种状态
分别是黑(完成标记,只包括自己以及和自己相邻的节点,三级节点不算)、灰(完成一半标记,只标记自身了)、白(未标记)
关于漏标
在并发标记阶段,(如果灰色下面的白色标记与灰色断开,并且这个白色又与黑色相连)。那么就会产生漏标,因为黑色已经标记完成,不会再次扫描
解决漏标的方法
因为漏标有两个必要条件(如果灰色下面的白色标记与灰色断开,并且这个白色又与黑色相连)
所以破坏其中之一即可解决
1、如果(白色又与黑色相连)把黑色重新标记为灰色即incremental update--批量更新,下次重新扫描(CMS解决方案)
2、如果(灰色下面的白色标记与灰色断开)把灰色与白色的引用保留到GC的堆栈中,这样白色还能被GC扫描到。其实就是灰白程序中无引用,但是GC给做了一份引用,等重新扫描的时候,扫描引用集合就可以了。SATB(snapshot at the beginning,留一份快照)G1的解决方案
为什么G1用SATB,不用incremental update?
扫描次数变少了,如果黑色变为灰色,那么灰色还需要再次扫描一遍
下次扫描时,由于有RSet的存在,不需要扫描整个堆去查找白色引用