一、三种垃圾清理方法
标记清除:扫描两边 + 内存碎片 — (优点)效率高,速度快
复制:对象移动 + 内存利用率低 — (优点)无内存碎片,清理速度快
标记整理:扫描两边 + 对象移动 + 效率低 — (优点)无内存碎片,内存利用率高
二、对象的生命周期
尝试在栈中分配 — 进入eden区 — 发生GC — 进入survivor1区 — 进入survivor2区 — 交替在survivor区移动 — 进入old区 — FullGC被清理
三、对象所在空间
- 栈上分配的对象
- 线程私有的小对象
- 对象不会逃逸(方法外没有引用指向该对象,也就是不会被其他线程引用)
- 支持标量替换(对象只包含一些标量属性,可以用这些标量来代表该对象)
- 线程本地分配对象TLAB:Thread Local Allocation Buffer(Eden区)
- 默认给每个线程划分Eden区1%的空间,该线程私有
- 多线程时线程间不用争用空间
- 小对象
- 老年代:大对象
四、对象什么时候进入老年代
-
PS垃圾回收器:15
CMS垃圾回收器:6
G1: 15 -
动态年龄:当survivor中的对象占用超过50%的单个survivor空间,把最老的放入老年代
五、常见垃圾回收器(目前所有垃圾回收器都会有STW阶段)
PS — PS — CMS — G1 — ZGC
PS支持几十兆内存
PS支持几百兆内存
CMS支持20G内存
G1支持上百G内存
ZGC支持4T内存
-
Serial:单线程
所有线程到safe point(线程安全点)后,执行STW(stop the world),工作线程都停止,一个线程执行GC -
PS + PO:多线程
和Serial一样需要STW,工作线程有停止时间,只不过该方案使用多线程来共同清理垃圾 -
ParNew(ParNew是PS的升级版,为了能和CMS配合使用)
-
CMS:开启了并发回收的新阶段,但毛病多(适合小内存服务器,服务器内存升级可能反而降低了程序执行效率)
1) CMS存在的问题:
由于使用的是标记清理算法(比作小伙子的速度),老年代内存空间会有越来越多的碎片,当碎片过多时会使用Serial Old(比作老奶奶的速度)回收器单线程标记整理老年代内存空间(服务器内存空间很大的话这里会消费大量时间)解决方法:调整预留给浮动垃圾的内存空间大小的参数,让老年代保持有足够的空间 – Concurrent Mode Failure –XX:CMSInitiatingOccupancyFraction 92%
2) 使用的算法:
三色标记 + Incremental Update -
G1
G1逻辑分代,物理不分代,其余逻辑物理都是分代的(少数最新的回收器逻辑物理都不分代),新老年代内存空间比例是动态分配的
为什么G1的效率更高:G1以前的垃圾回收器都有一个共同的问题,就是需要扫描整个年轻代或者老年代的内存空间然后再进行垃圾回收,如果内存空间很大,一次扫描的时间会非常久,STW时间过大;G1在物理上不分年轻代和老年代,而是将整个内存空间分成一个个不同容量的块,工作线程在使用某几个内存块的时候G1可以并发并行回收没有被使用的其余内存块(本质也是延续CMS的优点,使得工作线程和垃圾回收线程同时进行),优先回收垃圾最多的内存块Garbage First
G1每一块分区并没有特定的逻辑分类,每次被GC后都有可能重新选择成为一块新生代或者老年代逻辑内存分区;G1同样存在YGC和FGC,此外还有MixedGC;可以调小触发MixedGC(相当于CMS)的阈值来尽量减少FGC,相当于让CMS尽早发生以免触发FGC,因为G1的FGC是串行的,效率低,所以G1的调优目标之一就是消灭FGC;(或者增大服务器的内存,提高CPU的性能)
G1高效回收的关键点:Remembered Set
每一个Region内存块中有一块区域存放该set表,记录了其他引用了本Region中的对象的Region,那么当G1要回收一个Region的时候只需要读取他自己的这个RSet就可以知道对象的根路径情况,不需要再和以前的算法一样进行根搜索了。
六、GC的常用指令和配置参数
-Xmn(年轻代)
-Xms(最小堆)
-Xmx(最大堆)
-Xss(栈大小)
-XX:+PrintGCDetails(打印GC日志的详细信息)
-XX:+DisableExplictGC(禁止程序中手动调用System.gc()—FGC,上线后一般开启该设置)
-XX:+PrintFlagsFinal -version | grep xxx(查询JVM中和xxx相关的指令)
-XX:MaxTenuringThreshold(指定升代年龄)
-XX:UseConcMarkSweepGC(开启CMS)
-XX:CMSInitiatingOccupancyFraction(老年代使用多大比例后开始CMS)
-XX:CMSFullGCsBeforeCompaction(执行多少次CMS后进行一次内存标记压缩,应对内存碎片)
-XX:MaxGCPauseMillis(G1调优最频繁设调整的参数,GC时STW的最大停顿时间,G1会尝试自适应调整yong区大小来实现)
-XX:G1HeapRegionSize(G1的region大小,越小gc越频繁,但每次gc停顿时间越短,只能在1,2,4,8,16,32M中选择——ZGC优化为动态自适应调整)
常用垃圾回收器组合设置指令
-XX:+UseSerialGC = Serial New (DefNew) + Serial Old
-XX:+UseConc (urrent) MarkSweepGC = ParNew + CMS + Serial Old
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】
-XX:+UseG1GC = G1
七、各类GC的底层算法
并发标记算法:
1. 三色标记(CMS、G1)
黑:对象本身以及对象的成员变量对象均已被标记
灰:对象本身被标记完成,对象的成员变量没有完成标记
白:没有遍历到的对象
为什么G1需要二次标记?
漏标:并发标记时工作线程会改变某些节点之间的引用关系
1)黑色对象指向一个白色对象;
2)原来灰色对象与该白色对象之间的连接断开,由于黑色节点不会再搜索他指向的对象,所以该白色对象就不会再被标记改变颜色,产生漏标现象
- 解决方法:针对以上两个条件中的任意一个
方法1:(增量更新)黑色转灰色:当工作线程指向一个白色节点的时候,工作线程将该黑色节点重新标记为灰色,重复标记时就会标记该白色节点;
——CMS采用(需要重新扫描整个堆查找指向白色对象的引用,效率低)
方法2:(SATB)引用入栈+RSet:在开始并发标记前进行快照,记录所有灰色到白色节点的引用,当工作线程删除某一处引用时将该引用入栈,保证该引用的白色节点能够被标记。
——G1采用(利用RSet不需要重新扫描堆,效率高)
重新标记阶段读取栈中的引用,扫描每个引用指向的对象所在的region,读取该region的RSet查找是否有其他region的对象指向该对象,来决定是否要回收该对象
2. 颜色指针算法(ZGC……)
去除了G1中的RSet,将其他对象对本对象的引用直接标记在引用指针上(一个引用有64位,其中取三位作为标记),GC信息记录在引用指针上,而不是记录在对象头上,那就不需要经常去修改堆中对象的数据,直接修改引用指针;