1、垃圾收集器算法
垃圾收集算法是结合堆中不同对象的生命周期,将对象进行分代。生命存活率低的存放在年轻代,而对象生命存活率高的放在老年代。
- 复制算法的效率非常高,将存活对象转移至suvrior区即可。
- 标记整理算法是标记清除进化的过程。标记清除算法非常简单,只需要把存活对象或垃圾标记标记,把存活对象留下,垃圾对象清除即可。但这存在着问题,会使内存空间碎片化,于是有了标记整理算法。
- 标记整理算法。标记整理算法在标记存活和垃圾对象后,不会直接将垃圾清除。而是将存活对象移动到内存一端,将域外的内存全部清理。
2、垃圾收集器类型
随着生产中机器物理内存的不断增大,垃圾收集器的能力势必需要更新迭代。
2.1 Serial收集器
Serial收集器是串行垃圾收集器,它只有1条垃圾收集线程,其处理能力适配单核、几M-100M的内存空间。否则在其垃圾回收期间,服务停顿的时间就会太长。
-XX:+UseSerialGC -XX:+UseSerialOldGC
2.2 PerNer 收集器
新生代收集器,被看做是Serial的多线程版本。处理能力在百M-1G。
-XX:+UseParNewGC参数指定ParNew来处理新生代的垃圾回收任务
2.3 Parallel Scavenge 收集器(JDK8默认)
parallel 收集器和PerNer十分相似。不同的是,Parallel 更注重吞吐量(CPU的利用率)。
-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代)
- 默认使用同CPU核数相同的线程进行垃圾清理
- 新生代采用复制算法,老年代使用标记 - 整理算法
2.4 CMS(Concurrent Mark Sweep) 收集器
CMS默认使用清除-标记算法。该老年代处理器的目标是获取最短回收停顿。从服务的级别考虑就是优化用户体验,减少卡顿现象。
- Mark Sweep 示意着收集器的算法是标记 - 清除。所以会存在内存碎片的问题。
- 初始标记:暂停应用程序线程,标记gc root下的直接引用对象为根节点,响应速度很快。
- 并发标记:与用户线程并发进行。从gc root下直接引用的对象开始,遍历整个对象图。该过程耗时较长,约占垃圾收集的80%,但由于是并发进行,所以用户不会有卡顿感受。但因过程中与用户线程并发,已标记的对象状态有可能发生改变。
- 重新标记:由于并发标记过程中,标记不准确。所以此时暂停用户线程(S-T-W),对前标记的数据使用三色标记法进行修正。
- 并发清理:此时清理线程将对没有被标记的对象进行清理。由于新生成的对象会标记为黑死,所以并发清理并不会影响新生成对象。
- 并发重置:重置本次gc过程的标记值。
注意事项:
- 并发表及和并发清理阶段都是与用户线程并发的,所以会出现已经标记好的对象状态出现修改,从而再次出现垃圾对象。那么只能等到下次gc再去清理。
- 由于使用Mark Sweep 标记-清除算法,会导致内存的碎片化。可以通过参数设置,对清除后的内存进行整理。
-XX:+UseCMSCompactAtFullCollection - 并发异常处理。并发标记和并发清理过程中,会出现用户线程和垃圾收集线程共同工作的问题,如果此时此时再次触发full gc。将出现"concurrent mode failure",此时会进入stop the world,用serial old垃圾收集器来回收。
1、-XX:+UseConcMarkSweepGC:启用cms
2. -XX:ConcGCThreads:并发的GC线程数(一般不主动修改,默认使用CPU核数即可)
3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)
4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次
5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)
6. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
7. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少老年代对年轻代的引 用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段
8. -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW
9. -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;
2.4.1 三色标记法
我们看到CMS收集器,存在3次并发过程。该过程中用户线程与垃圾收集器并发工作,是否会出现问题?用脚想都知道肯定有,那具体哪里出问题?会出现什么问题?hotspot又是怎么处理的?
1)哪里会存在问题?
单纯的使用serial和Parallel会不会出问题?如果实现时都是单纯的S-T-W,用户线程按下暂停键,收集线程出来打扫房间,此时不会有问题。那么对照CMS的处理器看,很明显,问题有可能出现在并发标记、并发清理、并发重置。(为什么?父母在屋里工作、小孩子在屋里乱扔乱造、同时保姆出来按照章程收集垃圾,保不齐小孩造的垃圾没收掉,把大人刚拿出来的书籍电脑整理掉了)
2)会出什么问题?
并发标记 —> 并发清理 :根据初始标记的Gcroots节点对象,一路向下遍历对象树。
- 2.1) 如果线程在运行过程中,其引用的对象下的某个成员对象不再使用了,变成了垃圾对象,怎么办?
答:多标-浮动垃圾对象。此种情况最好处理,多标了等待下次清理即可。 - 2.2) 内存中已有的对象,当完成该根节点下对象的扫描时,对象被标记为垃圾对象。而线程运行过程中,该对象又变成了有用对象,不能被清理,怎么办?
答:漏标-读写屏障。不同老年代收集器有不同的读写屏障实现方式,详见3.3三色标记的读写屏障。 - 2.3) 在并发清理时,新线程生成的对象,会被误清理吗?
不会被清理,在并发清理阶段,新增的对象直接标记为黑色。
3) 针对上述问题hotspot是怎么处理的? — 三色标记(ThreeColorRemark)
读写屏障(hotspot)
不同的老年代收集器实现了不同的读写屏障方式,总体分为2种方式来解决漏标问题。
一、增量更新(Incremental Update)
当黑色对象引用指向白色对象时,将这个新插入的引用记录下来。在重新扫描时,将一黑色节点为跟,将对象树下的对象重新扫描(或理解为原黑色对象引用白色对象后,它有变成了灰色,需要重新扫描)
二、原始快照(Snapshot At The Beginning,SATB)
将B节点要删除D对象时,将要删除的引用记录下俩,并发扫描后,在对灰色为跟节点将下面的所有对象重新扫描,将白色对象直接置为黑色,先让他活下来。
所以无论增量更新还是原始快照都是加上JVM自实现的写屏障,类似于JAVA的AOP代理机制
- CMS:写屏障+增量更新
- G1、Shenandoah:写屏障+SATB
- ZGC:读屏障
2.4.2 卡表和记忆集
hotspot中为了解决跨带引用导致的minor gc去大范围扫描老年代的问题,在年轻代中维护一张记忆集,用于记录老年代中是否存在引用年轻代的对象。如果记录值为1,则去老年代的卡表中查找具体引用年轻代对象的地址,进而标色。
3 G1收集器(推荐多核大容量内存)
-XX:+UseG1GC -XX:MaxGCPauseMillis(用户期望的STW时间)
3.1 G1内存模型
- 1、G1将堆空间划分为2^N次方个大小相等的独立区域,最多支持2048个Region。
- 2、每个Region的大小可以用堆内存/region个数,可以手动指定- XX:G1HeapRegionSize。一般推荐默认值
- 3、G1仍然有年轻代、老年代的逻辑概念,但物理上其内存不再连续。
- 4、年轻代初始空间默认为堆内存的5%(可以指定-XX:G1NewSizePercent)
- 在系统运行中,JVM会不停的给年轻代增加更多的Region,但是最多新生代的占比不会超过60%
- G1新增Humongous区,专门存放大对象。(大对象的判断,当对象所占空间超过Region空间的50%,当一个Region放不下对象时,此时需要连续的空间作为Humongous存放)
3.2 G1回收过程
回收过程,实际只在筛选回收部分与CMS有区别。CMS的并发清理和并发重置,在G1里替换为筛选回收。注意G1的筛选回收是S-T-W的,而后续Shenandoah则实现了筛选过程与用户线程并发。
我们知道初始标记、最终标记,实际的耗时很短,只要有效控制筛选回收的时间,既可以达到让人满意的停顿时间。
G1的重点工作就在这里,如何有效控制筛选回收的时间,保证在默认的200ms(或指定的时间)内完成垃圾回收。
- 过程中,G1有一套机制,专门用于评估回收哪些对象,比较如何在相同时间更高效率的回收更多内存。
- 年轻代、老年代的回收算法都是使用复制算法,复制完成后,原有区域变成可用区域,这样就节省了原CMS的内存碎片整理。
3.3 G1垃圾收集分类
- Young GC
前文描述了,年轻代初始化只占5%。那当Eden区放满后,是直接继续添加Eden内存还是要进行Young GC?G1会先计算收集当前Eden区垃圾对象需要耗费的时间,是否大于-XX:MaxGCPauseMills 设定的值。当收集时间需要超过预设允许的暂停时间,则需要触发Young GC。否则继续添加Eden内存。 - Mixed GC
当老年代的堆占有率达到阈值:XX:InitiatingHeapOccupancyPercent(默认45%)时触发Mixed GC,回收所有的Young和部分Old区域(主要看预期的停顿时间对老年区进行优化排序)以及大对象区。使用复制算法 - Full GC
当Mixed GC复制对象到新的Region时,如果没有足够多的Region区域来接收本地复制的对象,此时触发Full GC。停止用户线程,使用单线程进行标记–清理–整理,这样提供一批Region供Mixed GC复制。尽量避免这种STW的出现。(Shenandoah将单线程full gc做成多线程full gc)
4 推荐根据内存值适配的调优参数
4.1 内存1-4G(建议使用JDK8默认的Parallel)
理由:Parallel是采用多线程S-T-W的方式进行回收的,如果内存区域过大,线程回收垃圾的时间就很很长,停顿的时间对用户来说就会太明显。
- -XX:+UseParallelGC和-XX:+UseParallelOldGC
4.2 内存4-8G(ParNew+CMS)
理由:
1、ParNew的年轻代清理算法比较简单,多线程收集年轻代区域,年轻代内存也相对比较小,回收主要是复制算法,相对简单高效。
2、CMS采用三色标记法,整体算法相对Parallel Old相对复杂,但更专注于用户的暂停感受,使用并发垃圾回收(并发标记、并发清理、并发重置)的方式延长了垃圾回收整体时间,但缩短了真个Fu’llgc的S-T-W时间(只会在初始标记-重新标记时,会有暂停用户线程)。另外,CMS算法又明显比G1的回收机制简单,小内存状态下(4-8G),由G1回收内存的优化程度不足以弥补程序复杂度提升带来的效率缩减。
- ‐XX:+UseParNewGC ‐XX:+UseConcMarkSweepGC 内存4-8G,兼用使用parNew+CMS
- ‐XX:SurvivorRatio=8 配置Eden和survivor区的比例8:1:1
- ‐XX:MaxTenuringThreshold=5 对象进入老年代的分代年龄阈值 (5次)
- ‐XX:PretenureSizeThreshold=1M 对象直接进老年代的占用内存大小阈值)(1M)
- ‐XX:CMSInitiatingOccupancyFraction=92 老年代使用比例达到(默认92)会触发full gc
- ‐XX:+UseCMSCompactAtFullCollection CMS垃圾清除后是否需要内存碎片整理(是)
- ‐XX:CMSFullGCsBeforeCompaction=0 经历过多少次CMS垃圾清除后,进行碎片整理(配置上面开关使用),建议3次即可
‐Xms3072M ‐Xmx3072M ‐Xmn2048M ‐Xss1M ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐XX:SurvivorRatio=8 ‐XX:MaxTenuringThreshold=5 ‐XX:PretenureSizeThreshold=1M ‐XX:+UseParNewGC ‐XX:+UseConcMarkSweepGC ‐XX:CMSInitiatingOccupancyFraction=92 ‐XX:+UseCMSCompactAtFullCollection ‐XX:CMSFullGCsBeforeCompaction=3
4.3 内存8-64G(建议G1)
- -XX:+UseG1GC 使用G1收集器
- -XX:ParallelGCThreads:指定GC工作的线程数量 (一把指定CPU核数)
- -XX:G1HeapRegionSize:指定Region分区大小(1MB~32MB,且必须是2的N次幂),默认将整堆划分为2048个分区
- -XX:MaxGCPauseMillis:目标暂停时间(默认200ms)
- -XX:G1NewSizePercent:新生代内存初始空间(默认整堆5%)
- -XX:G1MaxNewSizePercent:新生代内存最大空间
- -XX:TargetSurvivorRatio:Survivor区的填充容量(默认50%),Survivor区域里的一批对象(年龄1+年龄2+年龄n的多个 年龄对象)总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代
- -XX:MaxTenuringThreshold:最大年龄阈值(默认15)
- -XX:InitiatingHeapOccupancyPercent:老年代占用空间达到整堆内存阈值(默认45%),则执行新生代和老年代的混合 收集(MixedGC),比如我们之前说的堆默认有2048个region,如果有接近1000个region都是老年代的region,则可能 就要触发MixedGC了
- -XX:G1MixedGCLiveThresholdPercent(默认85%) region中的存活对象低于这个值时才会回收该region,如果超过这 个值,存活对象过多,回收的的意义不大。
- -XX:G1MixedGCCountTarget:在一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收阶段可以回收一 会,然后暂停回收,恢复系统运行,一会再开始回收,这样可以让系统不至于单次停顿时间过长。
- -XX:G1HeapWastePercent(默认5%): gc过程中空出来的region是否充足阈值,在混合回收的时候,对Region回收都是基于复制算法进行的,都是把要回收的Region里的存活对象放入其他Region,然后这个Region中的垃圾对象全部清理掉,这样的话在回收过程就会不断空出来新的Region,一旦空闲出来的Region数量达到了堆内存的5%,此时就会立 即停止混合回收,意味着本次混合回收就结束了。
G1收