26.垃圾回收器

垃圾回收器

垃圾收集器没有JVM规范中明确定义,不同厂商不同版本的JVM具体实现

JDK每次迭代的时候都会对GC进行优化

java不同版本新特性

  • 语法层面: Lambda表达式 switch 自动装拆箱 enum
  • API层面: Stream LocalDate Optional String Collectors
  • 底层优化: JVM优化 GC优化 元空间 静态与 字符串常量池位置变化

垃圾收集器分类

按线程数分

  • 串行垃圾回收器和并行垃圾回收器

image-20200713083030867

  • 串行回收器在同一时间段只允许一个CPU执行垃圾回收,此时工作线程被暂停,直到垃圾收集工作结束.
  • 在诸如单CPU处理器或者较小的应用内存等硬件平台性能较差的场合,串行回收器的性能可以超过并行或者并发收集器
  • 在并发能力较强的CPU上,并行回收器产生的停顿时间要短语串行收集器
  • 和串行回收想法,并行收集器可以运用多个CPU同时执行垃圾回收,因此提高应用的吞吐量,不过并行回收仍然与串行一样,采用独占是,使用了STW机制

按工作模式分

  • 按工作模式分,可以分为并行式垃圾回收器和独占式垃圾回收器
  • 并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的停顿时间
  • 独占式垃圾回收器STW一旦运行,就停止应用程序中所有用户线程,直到垃圾回收过程完全结束
  • image-20200713083443486

按碎片处理方式分

  • 按碎片处理方式分,分为压缩式垃圾回收器和非压缩式垃圾回收器
  • 压缩式垃圾回收器会在回收完成之后,对存活对象进行压缩整理,清除回收后的碎片
  • 非压缩式的垃圾回收器不进行整理

按工作内存区域分

  • 新生代垃圾回收器和老年代垃圾回收器

评估GC的性能指标

  • 吞吐量: 运行用户代码的时间占总运行时间的比例

  • 垃圾收集开销: 吞吐量的补数,垃圾垃圾所用时间与总运行时间占比

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

  • 收集频率: 相对于应用程序的执行,收集操作发生的频率

  • 内存占用: java堆区所占内存大小

  • 快速: 一个对象从诞生到被回收所经历的时间

  • 吞吐量,暂停时间,内存暂用,三者构成不可能三角.三者总体的表现会随着技术进步越来越好.一款优秀的收集器通常最多同时满足其中两项.这三项里,暂停时间的重要性日益凸显.随着硬件发展,内存占用影响越来越小,硬件的提升有助于降低收集器运行时对程序的影响,即提高了吞吐量.而内存的扩大,对延迟反而带来负面效果.

性能指标 吞吐量

  • 吞吐量就是CPU用户运行用户代码时间和CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码的时间+垃圾收集时间)
  • 应用程序能容忍较高的暂停时间,因此,高吞吐量的应用程序有更长的事件基准,快速响应不必考虑,吞吐量优化,意味着在单位时间内,STW时间最短
  • image-20200713084726176

性能指标 暂停时间

  • 暂停时间指一个时间段内应用程序线程暂停,让GC线程执行的状态
  • GC期间100毫秒的暂停时间意味着在这10毫秒期间内没有应用程序线程是活动的。暂停时间优先,意味着尽可能让单次STW的时间最短:0.1+0.1 + 0.1+ 0.1+ 0.1=0.5
  • image-20200713085306400

吞吐量VS暂停时间

  • 吞吐量较好让程序用户的最终用户感觉只有应用线程在生产.直觉上吞吐量越高程序运行越快
  • 低暂停时间(低延迟)较好因为从最终用户的角度来看不管是GC还是其他原因导致一个应用被挂起始终是不好的.取决于应用程序的类型,有时候甚至短暂的200毫秒也会影响用户体验.有低较大暂停时间是非常重要的,特别是交互式引用程序
  • 吞吐量和暂停时间是此消彼长的状态,如果吞吐量优化那么必然需要降低内存回收的执行频率,就会导致每次执行GC所耗费的时间更长;相对的如果要求低延迟,为了每次都将STW控制在一个时间范围内,会导致GC执行的频率上升,导致GC总时间更长降低吞吐量
  • 在选择GC算法时,明确需求,一个GC算法只能侧重于吞吐量和暂停时间其中一个目标,或者尝试选择一个折中的方案
  • 现代硬件条件的基础上一般标准是:在最大吞吐量的情况下,降低停顿时间. 或者在一个可控停顿时间的基础上,最大化吞吐量

7种经典的垃圾收集器

  • 串行回收器: Serial , Serial Old
  • 并行回收器: ParNew , Parallel Scavenge, Parallel Old
  • 并发回收器: CMS , G1
  • image-20200713093551365
  • image-20200713093757644
  • 新生收集器: Serial , ParNew, Parallel Scavenge,
  • 老年生收集器: Serial Old, Parallel Old, CMS
  • 整堆收集器: G1

垃圾收集器的组合关系

image-20200713094745366

  • 两个收集器有连线说明可以搭配使用; Serial/Serial Old ; Serial/CMS; parNew/Serial Old; Parallel/Serial Old; Parallel Scavenge/Parallel Old;G1
  • 其中Serail Old 作为 CMS出现 Concurrent Mode Failure 失败的后备方案
  • (红色虚线)由于维护和兼容性测试成本,在JDK8中 将Serial+CMS 和 ParNew+Serial Old的组合声明为废弃(JEP173),并在JDK9中完全取消这些组合的支持(JEP214)
  • (绿色虚线)JDK14中: 弃用Parallel Scavenge+Serival Old的组合(JEP366)
  • (青色虚线)JDK14中: 删除CMS垃圾回收器(JEP363)

查看默认垃圾收集器

  • -XX:+PrintCommandLineFlags: 查看命令行相关参数(包含使用的垃圾收集器)
  • 使用jinfo -flag 相关垃圾回收器参数 JID

Serial回收器 串行回收

  • Serial回收器是最基本,历史最悠久的垃圾收集器了.JDK3之前新生代的唯一选择
  • Serial回收器作为HotSpot中client模式的默认新生代垃圾收集器
  • Serial收集器采用复制算法,串行回收和STW机制方式执行内存回收
  • 除新生代之外,Serial收集器还提供了对应的老年代收集器Serial Old收集器.Serial Old收集器同样采用了串行回收和STW机制,只不过内存回收算法使用的标记压缩算法
  • Serial Old是运行在Client模式下的默认老年代垃圾收集器
  • Serial Old在Server模式下有两个用途
    • 与新生代的Parallel Scavenge配合使用
    • 作为老年代的CMS收集器的后备方案
    • image-20200713100703799
  • Serial 收集器单线程收集器,但是他单线程的意义不仅说明他只会在一个CPU中工作,更重要的是他进行垃圾收集时,必须暂停其他所有工作进程,直到他收集结束
  • 优势:
    • 简单高效(与其他收集器单线程相比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获取最高的单线程收集效率
    • 运行在client模式下可用内存一般不大,可以在较短的时间内完成垃圾收集.只要不频繁发生,使用串行回收器是可以接受的
    • 在HotSpot虚拟机中,使用-XX:+UseSerialGC参数可以显示指定年轻代和老年代使用串行收集器,使用了+UseSerialGC参数等价于也使用了UseSerialOldGC

ParNew回收器 并行回收

  • ParNew收集器可以说是Serial收集器的多线程版本
    • ParNew是Parallel缩写,New说明只能处理新生代
  • ParNew收集器除了采用并行回收的方式执行内存回收外,两款收集器几乎没有任何区别.ParNew收集器在新生代中同样采用复制算法 STW机制
  • ParNew是很多JVM运行在Server模式下新生代的默认垃圾收集器
  • image-20200713102030127
  • 对于新生代,回收次数频繁,使用并行方式高效
  • 对于老年代,回收次数少,使用串行方式节省资源,减少CPU切换
  • 除了Serial外,只有ParNewGC核能CMS收集器配合工作
  • -XX:+UseParNewGC手动指定ParNew收集器为新生代垃圾收集器.并影响老年代
  • -XX:ParallelGCThreads: 限制线程数量,默认开启和CPU相同的线程数,不建议修改

Parallel Scavenge 回收器 吞吐量优先

  • HotSpot新生代除了ParNew收集器是并行收集以外,Parallel Scavenge收集器同样也采用了复制算法,并行回收和STW机制
  • 与ParNew收集器不同,Parallel Scavenge收集器的目标是达到一个可控制的吞吐量(Throughput),它也成为吞吐量优先垃圾收集器
  • 自适应调节策略也是Parallel Scavenge与ParNew重要区别
  • 高吞吐量则可以高效率的利用CPU时间,尽快完成程序运算任务.主要适合在后台运算而不需要太多交互的任务,.因此常在服务器环境中使用.例如批量处理,订单处理,科学计算等
  • Parallel收集器在JDK6提供了执行老年代垃圾收集的Parallel Old收集器,用来替代老年代的Serial Old
  • Parallel Old收集器采用了标记-压缩算法,同样也是基于并行回收和STW机制
  • image-20200713110359441
  • 在程序吞吐量优先的应用场景中,Parallel 收集器和Parallel Old收集器的组合,在Server模式下性能不错,是JDK8默认的垃圾收集器
  • 参数配置
    • -XX:+UseParallelGC 手动指定新生代使用Parallel并行收集器执行内存回收
    • -XX:+UseParallelOldGC 手动指定老年代使用Parallel并行回收器
      • 分别使用新生代和老年代 JDK8默认垃圾收集器
      • 两个参数开启一个另一个也会开启 相互激活
  • 默认情况下,当CPU小于8个,ParallelGCThread的值等于CPU数量
  • 当CPU数量大于8个,ParallelGCThreads的值等于3+(5*CPU)/8
  • -XX:MaxGCPauseMillis 设置垃圾收集器最大停顿时间(STW时间) 单位毫秒
  • 为了尽可能把停顿时间控制在MaxGCPauseMillis以内,收集器工作时会调整java堆大小或者其他一些参数.对于用户来讲,停顿时间越短体验越好.但是服务器端我们更注重高并发,整体的吞吐量.所以服务器更适合Parallel,进行控制.该参数使用需谨慎.
  • -XX:GCTimeRatio 垃圾收集器占总时间的比例 用于衡量吞吐量大小 取值(0,100)默认值99,也就是垃圾收集时间不超过1%
  • XX:MaxGCPauseMillis和 -XX:GCTimeRatio 相互矛盾,暂停时间越长,Ratio参数就容易超过设定的比例
  • -XX:+UseAdaptiveSizePolicy 设置Parallel scavenge收集器自适应调节策略,在这种模式下,新生代的大小,Eden和Survivor的比例,晋升老年代的对象年龄参数会被自动调整,已达到堆大小,吞吐量和停顿时间的平衡点
  • 在手动调优比较困难的场合,可以直接使用自适应的方式,仅指定虚拟机的最大堆空间,目标吞吐量(GCTimeRatio)和停顿时间(MaxGCPauseMillis)让虚拟机自己完成调优工作

CMS回收器 低延迟

  • JDK5时代,HotSpot推出一款在强交互应用中几乎认为有划时代意义的垃圾收集器 CMS(Concurrent Mark Sweep)收集器,这款收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,他第一次实现了让垃圾收集器线程与用户线程同时工作
  • CMS收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间.停顿时间越短(低延迟)就越适合于用户交互的程序,良好的响应速度能提高用户体验
  • 目前很大一部分java应用集中在B/S系统的服务端上,这类应用尤其重视服务响应速度,希望系统停顿时间最短,给用户带来较好的体验.CMS就非常符合这类应用的需求
  • CMS垃圾收集器采用的标记清除算法并且也会STW
  • CMS作为老年代的收集器,不能与新生代收集器Parallel Scavenge配合工作,所以在JDK5中使用CMS作为老年代收集器的时候,新生代只能使用ParNew或者Serial
  • image-20200713205154007
  • CMS整个过程比之前的收集器复杂,整个过程分为4个主要阶段,初始标记阶段,并发标记结算,重新标记阶段,并发清除阶段 其中涉及STW主要是初始标记阶段和重新标记阶段
    • 初始标记Initial-Mark: 这个阶段中,程序中所有工作线程都会STW出现短暂的暂停.这个阶段主要任务仅仅是标记处所有GCRoots能直接关联的对象,一旦标记完成之后就会恢复之前被暂停的应用线程.由于直接关联对象比较小,所以这里速度非常快
    • 并发标记Concurrent-Mark: 从GC Roots直接关联的对象开始遍历整个对象图过程,这个过程耗时较长,但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行
    • 重新标记Remark: 由于并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象标记记录,这个阶段停顿时间通常会比初始标记阶段更少,但远比并发标记阶段时间更短
    • 并发清理Concurrent-Sweep: 此阶段清理删除掉标记阶段中判断的不可达对象,释放内存空间.由于不需要移动存活对象,所以这个阶段也是可以与用户线程并发执行的
  • 尽管CMS收集器采用的是并发收集(非独占式),但是在其初始化标记和再次标记这两个阶段仍然需要STW暂停所有用户线程,不过暂停的时间很短.因此可以说明目前所有垃圾收集器都做不到完全不需要STW,只能是尽可能的缩短暂停时间
  • 由于最耗时的并发标记和并发清理阶段不需要暂停任务,所以整体的回收是低延迟的
  • 另外,由于执行GC的时候用户线程没有中断,所以在CMS回收过程中,还应该确保应用程序的用户线程有足够的内存可用.因此,CMS不能像其他收集器一样等到老年代几乎装满了再进行GC(如果快装满了,CMS才触发GC此时用户线程还在制造垃圾,如果GC的速度跟不上用户线程制造垃圾的速度就会导致可能有足够空间运行但是因为GC还没把垃圾清理完导致OOM)
  • CMS在当堆内存使用率达到某一个阈值时,便开始进行GC,以确保应用程序在CMS工作过程中依然有足够的空间支持应用程序运行.如果CMS运行期间预留的内存无法满足程序需要,就会出现一次Concurrent Mode Failure失败,这次虚拟机将启动后备方案,临时启用Serial Old收集器来重新进行老年代垃圾收集,这么停顿的时间就会很长
  • CMS收集器的垃圾收集算法采用的是标记清除算法,每次执行完成内存回收后,由于执行内存回收的无用对象所占用的内存空间极可能是不连续一些内存块,不可避免会产生内存碎片.CMS在为新对象分配内存空间的时候,不能使用指针碰撞(Bump the Pointer)只能使用空闲列表 Free List的方式
  • image-20200713212230352
  • CMS为什么不能使用标记压缩算法
    • 因为在并发清理的时候,如果用Compact整理内存的话,原来的用户线程还正在运行,会导致正在运行的对象内存地址方法变化.所以在保证用户线程还能继续执行的前提下,只能使用Mark-Sweep算法. Mark-Compact更适合STW的场景
  • CMS的优缺点
    • 优点
      • 并发收集
      • 低延迟
    • 缺点
      • 会产生内存碎片,导致并发清理之后,用户线程可用空间不足,无法分配大对象的情况,不得不提前触发Full GC
      • CMS收集器堆CPU资源敏感.在并发阶段,他虽然不会导致用户线程停顿,但是会因为占用了一部分线程导致应用程序变慢,导致总的吞吐量降低
      • CMS无法收集浮动垃圾(浮动垃圾指在用户线程和垃圾收集线程同时执行的时候用户线程新生成的垃圾).可能出现Concurrent Mode Failure失败导致另一次FullGC产生.在并发标记阶段由于程序的工作线程和垃圾收集线程同时运行或者交叉运行,那么在并发标记阶段如果产生新的垃圾对象,CMS将无法堆这些垃圾进行标记,最终会导致这些新产生的垃圾对象没有及时被回收,从而只能在下一次执行GC时才能回收这些内存空间
  • 设置参数
    • -XX: +UseConcMarkSweepGC 手动指定使用CMS
      • 开启改参数之后自动开启ParNewGC 新生使用ParNewGC 老年代使用CMS+Serial Old
    • -XX:CMSInitiatingOccupancyFraction 设置堆内存使用率阈值,一旦达到该阈值并开始进行回收
      • JDK5以前默认是68,当老年代空间使用率达68%时触发CMS回收.JDK6以上版本默认92%
      • 如果内存增长缓慢,可以设置一个稍大的值,大的阈值可以降低CMS触发的频率,减少老年代的回收次数有效改善程序性能.反之,如果应用程序内存使用率增长很快,则应该降低阈值,以避免频繁触发老年代串行收集器.因此通过选项可以有效降低FullGC执行次数
    • -XX:+UseCMSCompactAtFullCollection 用于指定在执行完FullGC之后对内存空间进行压缩整理,避免内存碎片.不过内存压缩整理过程是需要STW的,带来的问题就是停顿时间变长
      • -XX: CMSFullGCBeforeCompaction 设置在执行N次FullGC后堆内存空间进行压缩整理
      • -XX:ParallelCMSThreads 设置CMS线程数量
        • CMS默认启动线程数是(ParallelGCThreads+3)/4, ParallelGCThreads是年轻代并行收集器的线程数.当CPU资源紧张,受CMS收集线程的应用,应用程序的性能在垃圾回收阶段会变成非常糟糕

CMS小结

  • 最小化使用内存和并行的开销 Serial GC
  • 最大化应用程序吞吐量 Parallel Scavenge GC
  • 最小化GC中断 低延迟 CMS GC

CMS变化

  • JDK9 CMS被标记为Deprecate 如果在JDK9上使用 UseConCMarkSweepGC会受到一个警告
  • JDK14中 删除了CMS垃圾收集器 就算只用UseConcMarkSweepGC JVM不会报错只是给出warning,JVM会自动回退以默认GC方式启动JVM

G1回收器 区域化分代式

  • G1是一款服务端应用的垃圾收集器,主要这对配备多核CPU及大容量内存机器,以极高概率满足GC暂停时间同时,还兼具高吞吐量的特性
  • 随着业务越来越庞大,复杂,用户越来越多,没有GC就不能正常工作,而经常造成STW的GC又跟不上实际需求,所以才会有不断尝试堆GC进行优化. G1(Garbage-First)垃圾回收器在JDK7 update4之后引入,是当今收集器技术发展最前沿的成果之一
  • G1的目标在于在延迟可控的情况下获得尽可能高的吞吐量

Why Garbage First ?

  • G1是并行回收器,它将堆空间分割为很多不相关的区域(Region 物理上不连续).使用不同的Region来表示Eden,Survivor0,Survivor1,Old等
  • G1 GC有计划的避免在整个堆空间进行全区域的垃圾收集.G1跟踪各个Region里面的垃圾堆价值大小(回收所获得空间大小及回收所需时间),在后台维护维护一个优先列表,每次根据允许收集时间,优先回收价值最大的Region
  • 这种方式侧重点在于 回收垃圾最大量的Region 因此命名 垃圾优先 Garbage First 收集器
  • JDK7正式启用,在JDK9之后G1成为默认垃圾回收器,取代了CMS回收器以及 Parallel + Parallel Old组合,被Oracle官方成为 全功能垃圾收集器
  • CMS在JDK9中已经标记为过期(Deprecated).在JDK8还不是默认的垃圾回收器,需要使用-XX:+UseG1GC来启用

G1垃圾收集器的优点

  • G1使用了全新的分区算法
  • 并发与并行
    • 并发: G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算机的功能,此时用户线程STW
    • 并发: G1拥有与用户线程交替执行的能力,部分工作可以和应用程序同时执行.因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序情况
  • 分代收集
    • 从分代上看,G1依然属于分代性垃圾收集器,它会区分新生代和老年代,新生代依然存在Eden和Survivor区.但从堆结构来看,他不要求整个Eden区,新生代或者老年代都是连续的,也不再支持固定大小和固定数量
    • 将堆空间分为若干区域(Region),这些区域中包含了逻辑上新生代和老年代
    • 和之前的各类回收器不同,它同时兼顾新生代和老年代.对比其他回收器专注只在一个区域不同
    • image-20200713215105293
    • image-20200713215133839
    • 空间整合
      • CMS: 标记清除算法,会产生内存碎片,若干次之后GC会进行一次碎片整理
      • G1将内存划分成若干个Region.内存回收以Region为基本单位.Region之间是复制算法,但整体上实际可以看做标记压缩算法,两种算法都可以避免内存碎片.这种特性有利于程序长时间运行,分配大对象时不会因为找不到连续内存空间而提前触发FullGC.尤其在堆空间非常大的时候,G1的优势更加明显
    • 可预测的停顿时间模型 软实时 Soft Real-Time 这是G1相对于CMS另一个优势,G1除了追求低延迟外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不超过N毫秒
      • 由于分区原因,G1可以只选取部分区域进行,这样缩小回收的范围,因此对于全局停顿情况的发生也能得到较好的控制
      • G1跟踪各个Region里面垃圾堆积的价值大小(回收所获得空间大小和回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region.保证了G1收集器在有限的时间内获取尽可能收集效率
      • 相比CMS,G1未必能做到CMS最好的情况下低延迟,但是最差情况要好得多

G1垃圾收集器缺点

  • 相比CMS,G1不具备全方位,压倒性优势.比如在用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用(Footprint)还是在程序运行时的额外执行负载(Overload)都要比CMS要高
  • 经验上还说,在小内存应用上CMS表现大概率优于G1,在G1在内存应用上则发挥其优势.平衡点在6-8GB之间

G1参数设置

  • -XX:+UseG1GC 手动指定使用G1垃圾收集器执行内存回收任务
  • -XX:G1HeapRegionSize 设置每个Region大小,值是2的幂,范围是1MB到32MB之间,目标是根据最小的java堆大小划分出越2048个region.默认是对内存的1/2000
  • -XX:MaxGCPauseMillis: 设置期望达到的最大GC停顿时间指标(JVM尽力实现,但不保证达到).默认值是200ms
  • -XX:PrallelGCThread 设置STW工作线程值 最多设置8
  • -XX:ConcGCThread 设置并发标记的线程数,将n设置为并行垃圾回收的线程数(ParallelGCThread)的1/4左右
  • -XX:InitiatingHeapOccupancyPercent 设置触发并发GC周期的java堆占用率阈值.超过阈值就触发GC.默认值45

G1收集器常见操作步骤

G1的设计原则就是简化JVM性能调优,开发人员只需简单三步即可完成调优

  • 第一步: 开启G1垃圾收集器 -XX:+UseG1GC
  • 第二步: 设置堆的最大内存 -Xms -Xmx
  • 第三步: 设置最大停顿时间 -XX:MaxGCPauseMillis=%d

G1提供了三种垃圾收集模式: YoungGC MixedGC 和FullGC,在不同条件下被触发

G1收集器的应用场景

  • 面向服务应用,针对具有大内存,多处理器的机器.
  • 主要应用在需要低GC延迟,并具有大堆空间的应用程序提供解决方案
  • 堆大小约6GB或更大时,可预测的暂停时间可低于0.5; G1通过每次只清理一部分而不是全部Region的增量式清理来保证每次GC停顿的时间不会过长.用来替换掉JDK5中的CMS收集器 如下情况G1的性能可能比CMS好
    • 超过50%的java堆被活动数据占用
    • 对象分配频率或年代提升频率变化很大
    • GC停顿时间过长
  • HotSpot垃圾收集器中,除了G1外,其他的垃圾收集器使用内置的JVM线程执行GC多线程操作,而G1可以采用应用线程承担后台GC工作,当JVM的GC线程处理速度慢时,系统的调用应用程序线程帮助加速垃圾回收过程

分区Region 化整为零

  • 使用G1收集器时,它将整个java堆划分成2048个大小相同的独立region,每个region大小根据堆空间实际大小而定,整体被控制在1MB到32MB之间,且为2的N次幂,即1MB,2MB,4MB,8MB,16MB,32MB。
  • -XX:G1HeapRegionSize设定.所有region大小相同,且在JVM生命周期内不会被改变
  • 虽然保留新生代和老年代的概念,但新生代和老年代不再是物理隔离.他们都是region(不需要连续)的集合.通过region的动态分配方式实现逻辑上的连续
  • image-20200713223244886
  • 一个region可能属于Eden Surivior Old.但是一个region只可能属于一个角色.图中的E表示该region属于Eden内存区域,s表示属于survivor内存区域,o表示属于old内存区域。图中空白的表示未使用的内存空间。
  • G1还新增了一个新的内存区域Humongous内存区域,如图中的H块.主要用于存储大对象,如果超过1.5个region就放到H
    • 设置H的原因: 对于堆中对象,默认直接会被分配到老年代,但是如果他是一个短期存在的大对象就会被垃圾收集器造成负面影响.为了解决这个问题,G1划分了一个Humongous区,专门用来存放大对象.如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储.为了能找到连续的H区,有时候不得不启动FullGC.G1大多数行为都会把H区作为老年代的一部分来看待
    • 每个region都是通过指针来碰撞分配空间
    • image-20200713223509993

G1垃圾收集器回收过程

G1垃圾回收主要分为如下三个环节

  • 年轻代GC YoungGC
  • 老年代并发标记 Concurrent Marking
  • 混合回收 Mixed GC
  • 强力回收,单线程,独占式,高强度的FullGC还是继续存在 他针对GC评估失败提供一个失败保护机制

image-20200713224113996

  • 顺时针 young gc -> young gc + concurrent mark -> mixed gc
  • 应用程序分配内存,当年轻代的Eden区用尽时开始新生代回收过程,G1的新生代收集阶段是一个并行的独占式收集器.在新生代回收期间,G1暂停所有应用线程,启动多线程执行新生代回收.然后从新生代区间移动存活对象到Surivior的region或者老年代的region,也有可能是两个区间都会涉及
  • 当堆内存使用达到一定阈值(默认45%)时,开始老年代的并发标记过程
  • 标记完成马上开始混合回收过程,对于一个混合回收期,G1从老年代region移动存活对象到空闲区间,这些空闲区间成为新的老年代region. 跟其他GC不同的是,G1老年代不需要回收整个老年代,一次只需要扫描回收一部分老年代的region.同时混合回收是老年代region和新生代region一起回收
  • 举个例子:一个Web服务器,Java进程最大堆内存为4G,每分钟响应1500个请求,每45秒钟会新分配大约2G的内存。G1会每45秒钟进行一次年轻代回收,每31个小时整个堆的使用率会达到45%,会开始老年代并发标记过程,标记完成后开始四到五次的混合回收。

记忆集 Remembered Set

  • 一个对象被不同的区域引用的问题
  • 一个region不可能是孤立的,一个region中的对象可能被其他任意的region对象引用,判断对象存活时,是否需要扫描整个java堆才保证准确?
  • 其他分代收集器,也存在一样的问题 G1尤为突出 回收新生代不得不扫描老年代,这样会降低Minor GC的效率
  • 解决方法
    • 无论是G1还是其他分代收集器,JVM都是使用Remembered Set来避免全局扫描
    • 每一个region都有一个对应的RS,每次reference类型数据写操作时,会产生一个write barrier暂时中断操作
    • 然后检查将要写入的引用指向的对象是否和该reference类型不在同一个region(其他收集器:检查老年代的对象是否引用了新生代对象);如果不同,通过card table把相关引用信息记录到引用指向对象所在region对应的RS中;进行垃圾收集时,在GC Roots枚举范围加入RS,就可以保证不进行全局扫描,也不会有遗漏
    • image-20200713224716715

G1回收过程 新生代GC YoungGC

  • JVM启动时,G1先准备好Eden区,程序在运行过程中不断创建对象到Eden区,当Eden空间耗尽,G1会启动一次 YoungGC
  • YGC时,首先G1暂停应用程序执行STW,G1创建回收集 collection set,回收集是指需要被回收的内存分段的集合,新生代回收过程的回收集包括新生代Eden和Survior区所有内存分段
  • image-20200713225100632
  • 分步介绍过程
    • 第一阶段: 扫描根
      • 根是指static变量指向的对象,正在执行方法调用链上的局部变量.根引用连同RS记录的外部引用所谓扫描存活对象的入口
    • 第二阶段:更新 RSet
      • 处理Dirty card queue 中的card,更新RSet.此阶段完成后,RSet可以准确的反应老年代堆所在内存分段中对象引用
    • 第三阶段: 处理RSet
      • 识别被老年代对象指向的Eden对象,这些被指向的Eden对象被认为存活的
    • 第四阶段: 复制对象
      • 此阶段,对象树被遍历,Eden区内存中存活的对象会被复制到Surivior区中空的内存分段,Survior区内存段中存活的对象如果年龄未达到阈值,年龄+1,达到阈值会被复制到OLD区中空的内存分段.如果Surivior空间不够,Eden空间的部分数据会直接晋升到OLD
    • 第五阶段 处理引用
      • 处理Soft Weak Phantom Final JNI Weak等引用.最终Eden空间数据为空,GC停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果

G1回收过程 并发标记过程 concurrent marking

  • 初始标记阶段: 标记从根节点直接可达的对象 这个阶段是STW,并且会触发一次YGC
  • 根区域扫描(Root Region Scanning): GC扫描survivor直接可达old区域对象,并标记被引用的对象.这个过程必须在YGC之前完成
  • 并发标记 concurrent marking: 整个堆中进行并发标记(和应用程序并发执行),此过程可能被YGC中断.在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那么这个区域会被立刻回收.同时,并发标记过程中,会计算每个区域的对象存活性(region中存活对象的比例)
  • 再次标记 remark: 由于应用程序持续运行,需要修正上一次标记的结果.这个阶段是STW的.G1采用了比CMS更快的初始快照算法 snapshot-at-the-beginning(SATB)
  • 独占清理 clean up :计算各个区域存活对象和回收比例,并进行排序,识别可以混合回收的区域.为下一个阶段做准备.该阶段是STW的,这个阶段并不会去做垃圾收集
  • 并发清理阶段: 识别并清理完全空闲的区域

G1回收过程 混合回收

  • 当越来越多的对象晋升到老年代,为避免堆内存被耗尽,JVM会触发一个混合垃圾收集器MixedGC, 该算法并不是一个old GC,除了回收整个Young Region,还会回收部分old region.需要注意的是回收一部分old region,而不是全部的老年代.可以选择哪些old region进行收集,从而可以对垃圾回收的耗时进行控制. Mixed GC并不是FullGC
  • image-20200713225810871
  • 并发标记结束以后,老年代中百分百为垃圾的内存分段被回收了,部分为垃圾内存分段被计算出来.默认情况下,这些老年代内存分段会被分8次 可以通过-XX:G1MixedGCCountTarget设置
  • 混合回收的回收集 collection set 包括八分之一的老年代内存分段,Eden区内存分段,Survivor内存分段.混合回收的算法和新生代的算法完全一样,只是回收集多了老年代的内存分段.具体过程请参考上面新生代回收过程
  • 由于老年代的内存分段默认分8次回收,G1会优先回收垃圾多的内存分段.垃圾占内存分段比例越高的,越优先被回收.并且有一个阈值会决定内存分段是否被回收
  • -XX:G1MixedGCLiveThresholdPercent: 默认为65%, 意思是垃圾占内存分段比例要达到65%才会被回收,如果垃圾占比太低,意味着存活的对象占比较高,复制的时候会花费更多时间
  • 混合回收并不是一定要进行8次.有一个阈值-XX:G1HeapWastePercent,默认值为10%,意思是允许整个堆内存空间有10%的空间被浪费,意味着如果发现可以回收的垃圾占堆内存比例低于10%,则不再进行混合回收,因为GC会花费更多的时间但是回收的资源缺很少

G1回收可选的过程 FullGC

  • G1的初衷就是要避免FullGC的出现.但是如果上述方式不能正常工作,G1会停止应用程序的执行STW,使用单线程的内存回收算法进行垃圾回收,性能会非常差,应用程序停顿时间会很长
  • 要避免FullGC的发生,一旦发生需要进行调整.什么时候会发生FullGC呢?比如堆内存太小,如G1在复制存活对象的时候没有空的内存分段可用.则会回退到FullGC,这种情况可以通过增大内存解决.导致FullGC的原因可能有如下两个
    • 回收阶段时候没有足够的to-space来存放晋升的对象
    • 并发处理过程完成之前空间耗尽

G1回收优化建议

  • 从Oracle官方透露出来的信息可获知,回收阶段(Evacuation)其实本也有想过设计成与用户线程一起并发执行,但做起来比较复杂,考虑得到G1只是回收部分region,停顿时间是用户可以控制的,所以并不迫切去实现,而选择把这个特性加入到G1之后出现的低延迟垃圾收集器ZGC中.另外,还考虑到G1不是仅仅面向低延迟,停顿用户线程能够最大幅度提高垃圾收集效率,为了保证吞吐量所以才选择完全暂停用户线程实现方法
  • 新生代大小
    • 避免使用-Xmn或者-XX:NewRatio等相关选项显示设置新生代大小
    • 固定新生代大小会覆盖
  • 暂停无时间设置不要过于严苛
    • G1 GC的吞吐量目标是90%的应用程序时间和10%的垃圾回收时间
    • 评估G1的吞吐量时,暂停时间不要过于严苛.过于严苛会带来更多的垃圾回收开销,而这些会直接影响到吞吐量

垃圾回收器总结

image-20200714075738203

image-20200714080151020

垃圾回收器选择

  • 优先调整堆大小让JVM自适应完成
  • 如果内存小于100M,使用串行Serial回收器
  • 如果是单核,单机程序,并且没有停顿时间要求,串行回收器 Serial
  • 如果是多CPU,需要高吞吐量,允许响应时间超过1s,选择并行或者JVM自己选择
  • 如果是多CPU.追求低延迟,需快速响应(如延迟不超过1s),使用并发收集器
  • 官方推荐G1 性能高 现在互联网项目B/S 基本都是使用G1
  • 没有最好的收集器,更没有万能的收集算法,针对特定场景,特定需求选择不同的收集器

面试

  • 垃圾收集算法有哪些? 如果判断一个对象是否可以被回收?
    • 清除算法
      • 标记清除算法 复制算法 标记压缩算法
    • 标记算法
      • 引用计数算法
      • 可达性分析算法 GC Roots
    • 被GC Roots直接或间接连接的可达对象,在finalization机制中可能会被复活的对象
  • 垃圾收集器工作的基本流程
    • 标记->清除->内存整理
  • 垃圾收集器相关各种常用参数

垃圾回收器新发展

  • GC仍然处于飞速发展中,目前默认选项G1在不断进行改进,很多我们原来认为的缺点,例如串行的FullGC CardTable扫描低效,都已经大幅改进,例如JDK10之后,FullGC已经是并行运行,在很多场景下,其表现还略优于ParallelGC的并行FullGC实现
  • 即是是SerialGC,虽然古老,但是简单的设计和实现未必就过时,他本身的开销,不管是GC相关的数据结构的开销,还是线程的开销都是非常小的,所以随着云计算的兴起,在serverless等新的应用场景下,Serial GC找到新的舞台
  • CMS在JDK9 标记为Deprecate 在JDK14直接被移除
  • Epsilon:A No-Operation Garbage Collector Epsilon垃圾回收器 无操作回收器
  • ZGC: A Scalable Low-Latency Garbage Collector (Experimental) 可伸缩的低延迟垃圾回收器 JDK14中还处于实验阶段

Open JDK12的Shenandoash GC

  • Open JDK12的shenandoash GC:低停顿时间的GC(实验性)
  • Shenandoah,无疑是众多GC中最孤独的一个。是第一款不由oracle公司团队领导开发的Hotspot垃圾收集器。不可避免的受到官方的排挤。比如号称openJDK和OracleJDk没有区别的Oracle公司仍拒绝在oracleJDK12中支持Shenandoah。
  • Shenandoah垃圾回收器最初由RedHat进行的一项垃圾收集器研究项目Pauseless GC的实现,旨在针对JVM上的内存回收实现低停顿的需求。在2014年贡献给OpenJDK。
  • Red Hat研发Shenandoah团队对外宣称,Shenandoah垃圾回收器的暂停时间与堆大小无关,这意味着无论将堆设置为200MB还是200GB,99.9%的目标都可以把垃圾收集的停顿时间限制在十毫秒以内。不过实际使用性能将取决于实际工作堆的大小和工作负载。

image-20200714090608807

这是RedHat在2016年发表的论文数据,测试内容是使用ES对200GB的维基百科数据进行索引。从结果看:

停顿时间比其他几款收集器确实有了质的飞跃,但也未实现最大停顿时间控制在十毫秒以内的目标。 而吞吐量方面出现了明显的下降,总运行时间是所有测试收集器里最长的。

总结

  • shenandoah Gc的弱项:高运行负担下的吞吐量下降。
  • shenandoah GC的强项:低延迟时间。

革命性的ZGC

  • ZGC与shenandoah目标高度相似,在尽可能对吞吐量影响不大的前提下,实现在任意堆内存大小下都可以把垃圾收集的停颇时间限制在十毫秒以内的低延迟。
  • 《深入理解Java虚拟机》一书中这样定义ZGC:ZGC收集器是一款基于Region内存布局的,(暂时)不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-压缩算法的,以低延迟为首要目标的一款垃圾收集器。
  • ZGC的工作过程可以分为4个阶段:并发标记 - 并发预备重分配 - 并发重分配 - 并发重映射 等。
  • ZGC几乎在所有地方并发执行的,除了初始标记的是STW的。所以停顿时间几乎就耗费在初始标记上,这部分的实际时间是非常少的。

image-20200714091201073

停顿时间对比

image-20200714091401511

虽然ZGC还在试验状态,没有完成所有特性,但此时性能已经相当亮眼,用“令人震惊、革命性”来形容,不为过。 未来将在服务端、大内存、低延迟应用的首选垃圾收集器。

image-20200714093243028

JDK14之前,ZGC仅Linux才支持。

尽管许多使用ZGC的用户都使用类Linux的环境,但在Windows和macos上,人们也需要ZGC进行开发部署和测试。许多桌面应用也可以从ZGC中受益。因此,ZGC特性被移植到了Windows和macos上。

现在mac或Windows上也能使用ZGC了,示例如下:

-XX:+UnlockExperimentalVMOptions -XX+UseZGC

AliGC

AliGC是阿里巴巴JVM团队基于G1算法,面向大堆(LargeHeap)应用场景。指定场景下的对比:

image-20200714093604012

当然,其它厂商也提供了各种别具一格的GC实现,例如比较有名的低延迟GC Zing

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值