CMS的缺点
吞吐量低
由于CMS在垃圾收集过程使用用户线程和GC线程并行执行,从而线程切换会有额外开销,因此CPU吞吐量就不如在GC过程中停止一切用户线程的方式来的高。
无法处理浮动垃圾,导致频繁Full GC
由于垃圾清除过程中,用户线程和GC线程并发执行,即用户线程仍在执行,则在执行过程中会产生垃圾,这些垃圾称为"浮动垃圾"。
如果CMS在垃圾清理过程中,用户线程需要在老年代中分配内存时发现空间不足,就需再次发起Full GC,而此时CMS正在进行清除工作,因此此时只能由Serial Old临时对老年代进行一次Full GC。
使用"标记-清除"算法产生碎片空间
由于CMS采用的是“标记-清除算法",因此产生大量的空间碎片,不利于空间利用率。为了解决这个问题,CMS可以通过配置
-XX:+UseCMSCompactAtFullCollection
强制JVM在FGC完成后対老年代进行压缩,执行一次空间碎片整理,但空间碎片整理阶段也会引发STW。为了减少STW次数,CMS还可以通过配置
-XX:+CMSFullGCsBeforeCompaction=n
在执行了n次FGC后,JVM再在老年代执行空间碎片整理。
在并发收集失败的情况下,Java虚拟机会使用其他两个压缩型垃圾回收器进行一次垃圾回收。由于G1的出现,CMS在Java 9中已被废弃。
三色标记算法 - 漏标问题引入
没有遍历到的 - 白色
自己标了,孩子也标了 - 黑色
自己标了,孩子还没标 - 灰色
第一种情况 ,已经标好了 ab,还没 d,如下,此时B=>D 消失,突然A=D了,因为 A已黑了,不会再 看他的孩子,于是 D 被漏标了!
漏标的解决方案
把 A 再标成灰色,看起来解决了?其实依然漏标!
CMS方案: Incremental Update的非常隐蔽的问题:
并发标记,依旧产生漏标!
于是产生了 G1!
G1(Garbage First)是一个横跨新生代和老年代的垃圾回收器。实际上,它已经打乱了前面所说的堆结构,直接将堆分成极其多个区域。每个区域都可以充当Eden区、Survivor区或者老年代中的一个。它采用的是标记-压缩算法,而且和CMS一样都能够在应用程序运行过程中并发地进行垃圾回收。
G1能够针对每个细分的区域来进行垃圾回收。在选择进行垃圾回收的区域时,它会优先回收死亡对象较多的区域。这也是G1名字的由来。
G1收集器(Garbage-First)
Hotspot 在JDK7中推出了新一代 G1 ( Garbage-First Garbage Collector )垃圾回收,通过
-XX:+UseG1GC
参数启用。和CMS相比,Gl具备压缩功能,能避免碎片向題,G1的暂停时间更加可控。性能总体还是非常不错的,G1是当今最前沿的垃圾收集器成果之一。
当今最前沿的垃圾收集器成果之一。
G1的特点
- 追求停顿时间
- 多线程GC
- 面向服务端应用
- 整体来看基于标记-整理和局部来看基于复制算法合并
不会产生内存空间碎片. - 可对整个堆进行垃圾回收
- 可预测的停顿时间
G1的内存模型
没有分代概念,而是将Java堆划分为一块块独立的大小相等的Region.当要进行垃圾收集时,首先估计每个Region中的垃圾数量,每次都从垃圾回收价值最大的Region开始回收,因此可以获得最大的回收效率.
G1将Java堆空间分割成了若干相同大小的区域,即region
包括
- Eden
- Survivor
- Old
- Humongous
- 其中,Humongous是特殊的Old类型,专门放置大型对象.
这样的划分方式意味着不需要一个连续的内存空间管理对象.G1将空间分为多个区域,优先回收垃圾最多的区域.
G1采用的是Mark-Copy ,有非常好的空间整合能力,不会产生大量的空间碎片
G1的一大优势在于可预测的停顿时间,能够尽可能快地在指定时间内完成垃圾回收任务
在JDK11中,已经将G1设为默认垃圾回收器,通过jstat命令可以查看垃圾回收情况,在YGC时S0/S1并不会交换.
Remembered Set
一个对象和它内部所引用的对象可能不在同一个Region中,那么当垃圾回收时,是否需要扫描整个堆内存才能完整地进行一次可达性分析?
当然不是,每个Region都有一个Remembered Set,用于记录本区域中所有对象引用的对象所在的区域,从而在进行可达性分析时,只要在GC Roots中再加上Remembered Set即可防止对所有堆内存的遍历.
G1垃圾收集过程
初始标记
标记与GC Roots直接关联的对象,停止所有用户线程,只启动一条初始标记线程,这个过程很快。
并发标记
进行全面的可达性分析,开启一条并发标记线程与用户线程并行执行。这个过程比较长。
最终标记
标记出并发标记过程中用户线程新产生的垃圾,停止所有用户线程,并使用多条最终标记线程并行执行。
筛选回收
回收废弃的对象。此时也需要停止一切用户线程,并使用多条筛选回收线程并行执行。
S0/S1的功能由G1中的Survivor region来承载,通过GC日志可以观察到完整的垃圾回收过程如下,其中就有Survivor regions的区域从0个到1个
红色标识的为G1中的四种region,都处于Heap中.
G1执行时使用4个worker并发执行,在初始标记时,还是会触发STW,如第一步所示的Pause
回收算法
依旧前面例子:
因此,还是能追踪到 D,如果不维护 rset,需要扫描其他所有对象!因此只需要扫描该 region 即可~
针对新生代的垃圾回收器共有三个:Serial,Parallel Scavenge和Parallel New。这三个采用的都是标记-复制算法。其中,Serial是一个单线程的,Parallel New可以看成Serial的多线程版本。Parallel Scavenge和Parallel New类似,但更加注重吞吐率。此外,Parallel Scavenge不能与CMS一起使用。
针对老年代的垃圾回收器也有三个:刚刚提到的Serial Old和Parallel Old,以及CMS。Serial Old和Parallel Old都是标记-压缩算法。同样,前者是单线程的,而后者可以看成前者的多线程版本。
CMS采用的是标记-清除算法,并且是并发的。除了少数几个操作需要Stop-the-world之外,它可以在应用程序运行过程中进行垃圾回收。在并发收集失败的情况下,Java虚拟机会使用其他两个压缩型垃圾回收器进行一次垃圾回收。由于G1的出现,CMS在Java 9中已被废弃[3]。
G1(Garbage First)是一个横跨新生代和老年代的垃圾回收器。实际上,它已经打乱了前面所说的堆结构,直接将堆分成极其多个区域。每个区域都可以充当Eden区、Survivor区或者老年代中的一个。它采用的是标记-压缩算法,而且和CMS一样都能够在应用程序运行过程中并发地进行垃圾回收。
G1能够针对每个细分的区域来进行垃圾回收。在选择进行垃圾回收的区域时,它会优先回收死亡对象较多的区域。这也是G1名字的由来。
100g内存时,到头性能.
且G1 浪费空间,fullgc 特别慢!很多阶段都是 STW 的,所以有了 ZGC!
ZGC
听说你是 zerpo paused GC?
Java 11引入了ZGC,宣称暂停时间不超过10ms,支持 4TB,JDK13 到了 16TB!
和内存无关,TB 级也只停顿 1-10ms
UMA
- NUMA
知道NUMA存在并且能利用,哪个CPU要分配对象,优先分配离得近的内存 - 目前不分代(将来可能分冷热对象)
ZGC 学习 Asul 的商用C4收集器
颜色指针
原来的GC信息记录在哪里呢?对象头部
ZGC记录在指针,跟对象无关,因此可以immediate memory reuse
低42位指向对象,2^42=4T JDK13 2^44=16T, 目前最大就 16T,还能再大吗???
后面四位伐表对象不同状态m0 m1 remapped finalizable
18为unused
灵魂问题
内存中有个地址
地址中装了01001000 , mov 72,到底是一个立即数,还是一条指令?CPU->内存,通过总线连接,-> 数据总线地址总线控制总线,所以看是从啥总线来的即可
主板地址总线最宽 48bit 48-4 颜色位,就只剩 44 位了,所以最大 16T.
ZGC 阶段
1.pause mark start
2.concurrent mark
3.relocate
4.remap
对象的位置改变了,将其引用也改变过去 - 写屏障(与 JMM 的屏障不同,勿等同!)
而 ZGC 使用的读屏障!
GC的优势在哪里
- 流行于现代的各大语言和平台
- 效率和稳定性
- 程序员不需要负责释放及销毁对象消除了不稳定性,延迟以及维护等几乎全部(普遍的)的可能
- 保证了互操作性
- 不需要与APIs之间交互的内存管理契约
- 与不协调的库,框架,应用程序流畅地交互操作