0050-垃圾回收器

1. 基础定义

1.1 按线程数分

按线程数分,垃圾回收器可以分为串行或者并行(指的是垃圾回收的线程)


1. 串行垃圾回收器

2. 并行垃圾回收器


在这里插入图片描述

1.2 按工作模式分

按工作模式划分,可以分为并发的垃圾回收器和独占式的垃圾回收器


1. 并发垃圾回收器

2. 独占式垃圾回收器


在这里插入图片描述

1.3 性能指标

1. 吞吐量

运行用户代码的时间占总运行时间的比例(总运行时间=程序运行时间+内存回收时间)

2. 暂停时间

执行垃圾收集时,程序的工作线程被暂停的时间

3. 内存占用

Java堆区所占用的内存大小

这三者共同构成一个“不可能三角”,类似CAP理论,现在的追求标准,在最大吞吐量优先的情况下,降低停顿时间

2. 垃圾回收器概述

1. 串行或者并行

  1. 串行回收器:Serial、Serial Old

  2. 并行回收器:ParNew、Parallel Scavenge、Parallel Old

  3. 并发回收器:CMS、G1


    在这里插入图片描述

2. 新生代或老年代

  1. 新生代收集器:Serial、ParNew、Parallel Scavenge

  2. 老年代收集器:Serial Old、Parallel Old、CMS

  3. 整堆收集器:G1


    在这里插入图片描述

3. 组合关系


在这里插入图片描述

  1. 连线表示可以搭配使用

  2. 红色虚线Serial+CMS、ParNew+Serial Old,在jdk8中标明为废弃,jdk9中移除这些组合

  3. 绿色虚线,Parallel Scavenge+Serial Old,jdk14中废弃

  4. jdk1.7u4 G1可用

  5. jdk8默认的垃圾回收器组合Parallel Scavenge + Parallel Old

  6. jdk9默认的垃圾回收器为G1

4. 查看当前虚拟机使用的垃圾回收器命令

  1. -XX:+PrintCommandLineFlags
-XX:InitialHeapSize=266122880 -XX:MaxHeapSize=4257966080 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
  1. 使用命令行指令:jinfo -flag 相关垃圾回收器参数 进程ID
C:\Users\Administrator>jps
23088 Launcher
23268 RemoteMavenServer
26520 Jps
12604
23788 LogUtilTest

C:\Users\Administrator>jinfo -flag UseParallelGC 23788
-XX:+UseParallelGC

3. Serial-串行回收器

3.1 Serial

1. 区域:新生代

2. 算法:复制算法

3. 特点:串行回收,STW

4. 回收图示


在这里插入图片描述

5. 启用参数


-XX:+UseSerialGC参数可以指定年轻代和老年代都是用串行回收器


等价于新生代用Serial GC且老年代用Serial Old GC

3.2 Serial Old

1. 区域:老年代

2. 算法:标记-压缩算法

3. 其余和Serial回收器一致

4. ParNew-并行回收器

1. 区域:新生代

2. 算法:复制算法

3. 特点:并行回收,STW

4. 回收图示


在这里插入图片描述

5. 启用参数


-XX:UseParNewGC:启用垃圾回收器


-XX:ParallelGCThreads:线程并行线程的数量,默认开启和CPU核数相同的线程数

5. Parallel-吞吐量优先回收器

5.1 Parallel Scavenge

1. 区域:新生代

2. 算法:复制算法

3. 特点:并行、STW

4. 与ParNew回收器的区别

Parallel Scavenge目标是达到可控制的吞吐量,同时拥有自适应调节策略

5. 回收图示


在这里插入图片描述

6. 启用参数

  • -XX:+UseParallelGC 手动指定年轻代使用Parallell垃圾回收器
  • -XX:+UseParallelOldGC 手动指定老年代使用Parallel垃圾回收器


    上述两个开启一个,另外一个自动开启
  • -XX:ParallelGCThreads 设置垃圾回收并行收集的线程数


    默认情况下,当cpu核数小于8时,默认线程数等于cpu核数;当cpu核数大于8时,线程数等于3+[5*cpu核数/8]

5.2 Parallel Old

1. 区域:老年代

2. 算法:标记-压缩算法

3. 特点:并行、STW

6. CMS-低延迟回收器

1. 区域:老年代

2. 算法:标记-清除算法

3. 特点:并发、STW

4. 回收图示


在这里插入图片描述

  • 初始标记(Initial-Mark):仅标记GC Roots能直接关联到的对象,此过程需要STW
  • 并发标记(Concurrent-Mark)阶段:从初始标记出的直接关联对象,开始遍历出整个对象图,垃圾收集器线程和用户线程并发运行
  • 重新标记(Remark):修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录

重新标记,重新从GC Root开始查找新关联的对象,并进行标记;而初始标记、并发标记两个步骤标记的对象,即使并发标记过程中已经没有相关引用了,也不会再去清除这些对象的标记(直到等到下一次GC发生的时候再去清除)

这一步需要重新确认所有存活的对象,因为下一步需要清除垃圾了,如果没有重新标记,那么会清除掉并发标记过程中新产生的对象

  • 并发清除(Concurrent-Sweep):清理删除标记阶段已经死亡的对象,释放内存

CMS为什么安全?

  1. 初始标记:没有安全问题,标记出来的都是可达对象
  2. 并发标记:根据上一步标记的GC Roots直接对象获取的对象图,有可能会有部分对象在这个阶段死亡(浮动垃圾,等待下一次垃圾回收),也有可能在这个阶段新增一部分GC Roots
  3. 重新标记:重新获取GC Root可达对象(不包括初始标记已经获取的),1,2,3可以得到当前所有存活的对象(包括一小部分浮动垃圾),根据空闲列表也就可以得到当前所有不可达对象
  4. 并发清除:清除3中的不可达对象

5. 为什么采用标记-清除算法

并发清除的时候,要保证用户线程的执行,同时这个时候不可达对象地址已经确定,因此可达对象不能修改地址,所以不能用标记-压缩算法(需要在STW环境下使用)

6. 优点

  • 并发收集
  • 低延迟

7. 缺点

  • 会产生内存碎片,无法分配大对象,提前触发Full GC
  • 无法清理浮动垃圾

8. 启用参数

  • -XX:UseConcMarkSweepGC 开启该参数后会自动将-XX:UseParNewGC打开,经ParNew(年轻代)+CMS(老年代)+Serial Old

  • -XX:CMSInitiatingOccupancyFraction=percent
    设置堆内存使用率的阈值,达到该阈值以后,开始回收。
    jdk5-68% jdk6以后92%
    如果内存增长缓慢可以设置较大的值,否者需要设置较低的值,降低Full Gc的次数

  • -XX:+UseCMSCompactAtFullCollection 指定在执行完Full GC后对内存空间进行压缩整理,可以避免内存碎片的产生,停顿时间会变长

  • XX:CMSFullGCsBeforeCompaction 设置多少次Full GC后对内存进行压缩

  • XX:ParallelCMSThreads 设置CMS的线程数量,默认(ParallelCMSThreads+3)/4

9. 说明
jdk9以后已经废弃,jdk14以后已经删除

7. G1-区域化分代式回收器

延迟可控的情况下获得尽可能高的吞吐量
1. 区域:全堆

2. 算法:region之间是复制算法,整体上是标记-压缩算法

3. 特点:重新调整了内存结构,还存在新生代和老年代,但是各自都不是连续的


原始的内存结构


在这里插入图片描述


现在的结构,每个格子称为一个Region


在这里插入图片描述

4. 可预测的时间模型


每个Region垃圾占比称为回收价值,垃圾占比越高回收价值越高


在给定的停顿时间N毫秒内,选取若干个高价值的Region进行回收,尽量将回收时间控制在N毫秒内

5. 回收过程

在这里插入图片描述


5.1 年轻代GC(Young GC)

  • 扫描根

根是static变量指向的对象,正在执行的方法调用链条上的局部变量。根引用连同Rset记录的外部引用作为扫描存活对象的入口

  • 更新Rset

处理dirty card queue中的card,更新Rset。此阶段完成以后,Rset可以准确的反映老年代对所在的内存分段中对象的引用

  • 处理Rset

识别被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活的对象

  • 复制对象

此阶段,对象树被遍历,Eden区内存存活的对象被复制到Survivor区中空的内存分段,Survivor区内存段中存活的对象如果年龄未达阈值,年龄加1,达到阈值会被复制到Survivor空的内存分段,如果Survivor空间不够,Eden空间的部分数据会直接晋升到老年代空间

  • 处理引用

处理Soft,Weak,Phantom,Final,JNI Weak等引用

5.2 老年代并发标记过程(Concurrent Marking)

  • 初始标记阶段

标记从根节点直接可达的对象,这个阶段是STW的,并且会触发一次年轻代的GC

  • 根区域扫描

G1 GC扫描Survivor区直接可达的老年代区域对象,并标记被引用的对象,这一过程必须再young GC之前完成

  • 并发标记

在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域对象中所有对象都是垃圾,那这个区域会被立即回收。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)

并发标记带来的问题

并发标记是和用户线程一起运行的,在标记过程中,有可能会产生新的根对象(再次标记时需要修正),原来的存活对象也可能会消失(浮动垃圾)

  • 再次标记

修正并发标记的结果,时STW的,G1中采用了比CMS更快的初始快照算法

  • 独占清理

计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域。为下阶段做铺垫。是STW的(这个阶段不会实际清理垃圾)

  • 并发清理

根据标记结果清理垃圾

5.3 混合回收(Mixed GC)


混合回收不等于Full GC;混合回收会回收整个Young Region同时也会回收一部分Old Region

6. Full GC


6.1 触发原因

  • Survivor复制存活对象到Old区的时候没有空的内存分段可用
  • 并发处理过程完成之前空间耗尽

6.2 Full GC处理方式


停止所有的应用程序,使用单线程的内存回收算法,进行垃圾回收

7. 使用方式

  1. 开启G1垃圾收集器(jdk1.7u4 G1后可用,jdk1.9以后默认为此收集器)
  2. 设置堆的最大内存
  3. 设置最大停顿时间

8. 常用参数设置

  • -XX:+UseG1GC 手动指定G1垃圾收集器执行内存回收任务
  • XX:MaxGCPauseMillis 设置期望达到的最大GC停顿时间指标,默认是200ms
  • -XX:InitiatingHeapOccupancyPercent=percent 设置触发GC周期的java堆占用率的阈值,超过此值,就触发GC,默认值时45

8. GC总结

截至jdk1.8的垃圾回收器


在这里插入图片描述


发展阶段


在这里插入图片描述

9. GC日志分析

1. 日志参数配置

  • -XX:+PrintGC 输出gc日志 类似-verbose:gc
  • -XX:+PrintGCDetails 输出gc的详细日志
  • -XX:+PrintGCTimeStamps 输入gc的时间戳(以基准时间的形式)
  • -XX:+PrintGCDateStamps 输出gc的时间戳(以日期的格式)
  • XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
  • -Xloggc:…/logs/gc.log 日志文件的输出路径

2. 回收日志


年轻代gc日志


在这里插入图片描述


Full GC


在这里插入图片描述


备注

  • GC和Full GC说明停顿类型,Full GC会发生较长时间的STW,应该尽量减少
  • Searial(Default New Generation)显示DefNew
  • ParNew(Parallel New Generation)显示ParNew
  • Parallel Scavenge显示PSYoungGen
  • G1显示garbage-first heap

GC日志可视化分析工具

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章