JAVA GC 垃圾回收

写一下欠了很久的一篇博客吧

对java的垃圾回收做一个学习的归纳总结

纯属个人学习理解,必然不够完善,欢迎在下面回复指出

 

那为什么要有垃圾回收呢

对象是建在哪里的呢? 对象用完了之后

如果不释放的话,一直在那里占用内存,是一种对资源的浪费

 

JVM

 

  

 

 

 

 

 

 

 

什么是GC?

 

 

 

 

 

 
传统GC算法
 
算法

Mark-Sweep

 

 

 

 

Mark-Sweep-Compact

 

 

 

Mark-Copy

 

 

 

分代

 

 

 

 

 

 
垃圾回收器

 

 

 

 

 
CMS
  • Mark-Sweep:清除完垃圾未压缩内存,存在内存碎片

  • 延时高:Old GC时会扫描清除整个Heap的垃圾。现在内存越来越大,延时也会随之增高

  • 废弃:JDK9已经移除

 

 
G1
 
特点
  • 软实时、低延时、高吞吐

  • 可设定暂停目标

  • 适用大内存、多处理器

  • Mark-Weep-Compact

  • JDK9默认,用于替代CMS

 

 
Heap Allocation

 

 

  • 传统

    • Heap划分连续,分为年轻代、年老代、永久代(JDK8为元空间)

    • 分代size固定

  • G1

    • 将Heap划分为若干个相等的Region,大概2000多个Region

      • -XX:G1HeapRegionSize = n default 2048

      • -XX:G1HeapRegionSize 1~32M

    • Heap划分不连续,分为年轻代、年老代、永久代(JDK为元空间)

      • Eden、Survivor、Old(Humongous)

      • Humongous(超过Region大小的50%则直接在Old区分配)

       

 
GC
  • G1 GC分为两个过程:Young GC、Mixed GC

  • Full GC使用Serial Old GC

 

 
Card Table

目的

  • 两个分区有引用关系,那么如何找到引用关系?

  • GC最早引入卡表的目的是为了对内存的引用关系做标记,从而根据引用关系快速遍历活跃对象

  • 每个Card 覆盖一定范围的Heap(一般为512Bytes)。G1的RSet是在Card Table的基础上实现的

  • 每个Region被分成了多个Card,在不同Region中的Card会相互引用

 

 
RSet

问题

  • Young GC、Mixed GC需要标记所有Region嘛?

    • Full GC才需要全部标记

  • Old Region对象可能持有Young Region对象引用?

  • 不同Region可能会相互引用?

 

引用关系

  • 分区内部有引用关系 N

    • 无论Young或者Old都不需要,因为回收粒度是一个分区,会遍历整个分区

  • 新生代分区到新生代分区之间有引用关系 N

    • 无论哪一种GC,都会全量标记回收整个Young区域

  • 新生代分区到老生代分区之间有引用关系 N

    • Young GC无需记录,Mixed GC会先Young GC,然后以Young分区作为根扫描

  • 老生代分区到新生代分区之间有引用关系 Y

    • 需要记录,Young GC时会以Old Region引用作为根扫描

  • 老生代分区到老生代分区之间有引用关系 Y

    • Mixed GC时可能只会回收部分Old Region

     

定义

记忆集RSet是一种抽象概念,记录对象在不同Region之间的引用关系,目的是为了加速垃圾回收的速度。RSet结构其实是一个HashTable,Key是引用Region的起始地址,Value是一个集合,里面的元素是Card Table的Index

 

 

 

 

 
CSet

定义

Collection Set(CSet),它记录了GC要收集的Region集合,集合里的Region可以是任意年代的。

 

 
Young GC

条件

当所有Eden Region被耗尽无法申请内存时,就会触发一次Young GC。

 

过程

  • STW?

  • 选择要收集CSet,整个新生代分区就是CSet

  • 并行任务处理

    • GC Roots扫描并处理

      • GC Roots直接引用的对象复制到新的Survivor区

    • 更新RSet

      • 借助Write Barrier存入到Dirty Card Queue

      • 排空Dirty Card Queue

    • 处理Old分区到Young分区的引用

      • 把RSet所在卡表对应分区所有的对象都认为是根,把这些根引用的对象复制到新的Survivor区

    • Object Copy(GC Roots可达的对象),Mark-Sweep-Copy算法

    • 释放CSet,把这些分区加入到自由列表(Free List)

    • 调整Young Region数目(主要是根据GC执行时间和目标停顿时间预测下次可能发生GC能接受的最大数目)

     

 

 

 

 

 

 

调优

  • G1记录每个阶段的时间,⽤于⾃动调优

  • 记录Eden/Survivor的数量和GC时间

    • 根据暂停⽬标⾃动调整Region的数量

    • 暂停⽬标越短,Eden数量越少,吞吐量越低

     

 
三色标记法(并发标记)

难点

并发标记的难点在于GC线程在标记对象过程中,应用线程可能正在改变对象引用关系,从而造成漏标和错标

 

定义

  • 白色:未被标记

  • 灰色:自身已被标记,但是拥有field未被标记

  • 黑色:自身和拥有field都已标记

 

 

 

 

并发标记

  • 问题

    • 应用线程插入了一个从黑色对象到该白色对象的引用

    • 应用线程删除了所有从灰对象到该白对象的直接或者间接引用

     

 

 

 

  • 解决方案

    • 对于第一个条件,如果该对象是new出来的,并没有被灰对象持有,会漏标嘛?

      • Region中有两个top-at-mark-start(TAMS)指针,分别为prevTAMS和nextTAMS。在TAMS以上的对象是新分配的,这是一种隐式的标记

      • Remark阶段仍然会被标记到的

    • 对于GC已存在的对象,如果它还活着,必然会有一个灰对象直接或间接引用,如果该引用删除了或者被替换了,非常严重的错误?

      • SATB(Snapshot-At-The-Beginning),GC 开始时活着对象的一个快照

      • SATB破坏了第二个条件。一个对象的引用被替换时,可以通过write barrier将旧引用记录下来

      • 造成浮动垃圾

 
Mixed GC

过程

混合回收可以总结为两个阶段:

  • 并发标记

    • 条件:-XX:InitiatingHeapOccupancyPercent=N 45 by default(non_young_capacity_bytes)

    • 初始标记子阶段

      • STW

      • piggybacked on a normal Young GC

      • 根据Survivor分区作为根

    • 并发标记子阶段

      • 不暂停应用线程

      • 根据Survivor分区以及Old区的RSet开始并发标记

      • 每个GC线程每次只扫描一个分区(标记所有分区),从而标记存活对象,计算存活数量

    • 重新标记子阶段

      • STW

      • 从根(Survivor)出发,并发标记子阶段已经追踪了所有存活对象

      • 所有的引用变更都被处理了;这里的引用变更包括新增空间分配和引用变更,新增的空间所有对象都认为是活的,引用变更处理SATB。

      • 并行执行

        • -XX:ParallelGCThreads

    • 清理子阶段

      • 只清理没有存活对象的Region,不需要STW

  • Mixed GC

    • STW

    • G1HeapWastePercent=N,只有达到了才会触发Mixed GC(不一定立即发生)

    • 选择若干个Region进行

      • -XX:G1MixedGCLiveThresholdPercent=N (default 85),存活对象占比,只有在此参数之下,才会选中到CSet中

      • 根据暂停目标选择垃圾最多的Old Region优先进行----【G1名称由来】

        • -XX:G1MixedGCCountTarget=N,一次并发标记过后,最多执行Mixed GC的次数

      • Eden+Survivor Region+Old Region开始垃圾回收

 

 
Full GC

条件

  • 并发模式失效

    • Mixed GC之前,Old区就没剩余空间了,此时放弃标记

    • 调整Heap大小

  • 晋升失败

    • 完成标记,没有足够空间供存活对象或晋升对象

    • -XX:InitiatingHeapOccupancyPercent 降低并发标记阈值

    • 调整Heap大小

    •  -XX:ConcGCThreads 增加并发标记线程数

  • 疏散失败

    • Survivor空间和老年代中没有足够的空间容纳所有的幸存对象

    • -XX:InitiatingHeapOccupancyPercent 降低并发标记阈值

    • 调整Heap大小

    •  -XX:ConcGCThreads 增加并发标记线程数

Full GC是Serial Old GC,停顿时间很长,避免它触发

 

 
停顿预测模型

G1是一个响应时间优先的GC算法,用户可以设定整个GC过程的期望停顿时间,由参数MaxGCPauseMillis控制,默认值200ms。不过不是硬性条件,只是期望值。

衰减平均值和方差计算

 

 

 
G1调优

重要参数

  • 启用G1算法

    • -XX:+UseG1GC

  • GC暂停时间目标

    • -XX:MaxGCPauseMillis = 200 软目标

  • GC并发标记开始

    • -XX:InitiatingHeapOccupancyPercent = 45

 

最佳实践

  • 不要设置年轻代大小

    • G1将不再遵守暂停时间目标。因此,实质上,设置年轻一代的大小会禁用暂停时间目标。

    • G1不再能够根据需要扩展和缩小年轻一代的空间。由于尺寸是固定的,因此无法更改尺寸

  • 响应时间目标

    • 90%或更多时间内能达到的目标值,不要设置过小,太小会频繁Young GC

  • 避免疏散失败

    • 增加Heap大小

      • 增加-XX:G1ReservePercent=n,默认值为10

      • G1试图通过使备用内存空闲以防万一,希望有更多的“空间”

    • 提前进行并发标记

      • -XX:InitiatingHeapOccupancyPercent 降低该阈值

    • 使用该-XX:ConcGCThreads=n选项增加标记线程的数量。

 

 

ZGC

一款还未普遍使用的低延迟垃圾回收器,主要从美团技术抄写并理解

The Z Garbage Collector)是JDK 11中推出的一款低延迟垃圾回收器,它的设计目标包括:

  • 停顿时间不超过10ms;
  • 停顿时间不会随着堆的大小,或者活跃对象的大小而增加;
  • 支持8MB~4TB级别的堆(未来支持16TB)。

 

参考链接

posted @ 2020-12-28 21:49  我不随便起名字  阅读(68)  评论(0编辑  收藏  举报