垃圾回收器

什么是垃圾回收器

垃圾收集器 (GC) 自动管理应用程序的动态内存分配请求。

默认选择

垃圾收集器、堆大小和运行时编译器默认选择:

  1. 垃圾优先使用 (G1) 收集器
  2. 最大 GC 线程数受堆大小和可用 CPU 资源的限制
  3. 初始堆大小为物理内存的 1/64
  4. 最大堆大小为物理内存的 1/4
  5. 分层编译器,同时使用 C1 和 C2

GC的性能指标

  1. HotSpot VM 垃圾收集器可以配置为优先满足以下两个目标之一:最小暂停时间和最大吞吐量。如果达到首选目标,收集器将尝试最大化另一个。
  2. 垃圾收集开销:吞吐量的补数,垃圾收集所用时间与总运行时间的比例。
  3. 收集频率:相较于应用程序的执行,收集操作发生的频率。
  4. 快速:一个对象从诞生到被回收所经历的时间。
  5. 内存占用:Java堆区占用的内存大小

暂停时间

  1. "暂停时间"指一个时间段内应用程序线程暂停,让GC线程执行的状态
  2. 暂停时间优先意味着尽可能让单次STW的时间嘴短

吞吐量

  1. 吞吐量:运行用户代码时间/(垃圾收集所花费的时间 + 运行用户代码时间)。
  2. 吞吐量优先意味着在单位时间内STW的时间最短

小结

  1. 一个GC算法只能针对两个目标之一
  2. 现在标准:在最大吞吐量优先的情况下,降低停顿时间

GC与垃圾分代的关系

  1. 新生代收集器:Serial、ParNew、Parallel Scavenge
  2. 老年代收集器:Serial Old、Parallel Old、CMS
  3. 整堆收集器:G1

Serial回收器(串行回收)

  1. Serial收集器使用单个线程来执行所有垃圾收集工作,这使得它相对高效,因为线程之间没有通信开销。
  2. Serial 收集器采用复制算法、串行回收和"Stop-The-World”机制的方式执行内存回收。
  3. Serial收集器还提供用于执行老年代垃圾收集的serial old收集器。Serial old收集器同样也采用了串行回收和"stop the world"机制,只不过内存回收算法使用的是标记-压缩算法。
  4. 显式启用Serial收集器: -XX:+UseSerialGC。
  5. 现在一般不用Serial收集器

ParNew回收器(并行回收)

  1. Serial收集器和ParNew收集器之间的主要区别在于ParNew收集器具有多个线程。
  2. 对于新生代,垃圾次数频繁,使用并行方式高效
  3. 对于老年代,回收次数少,使用串行方式节省资源
  4. 在单个cPU的环境下,ParNew收集器不比Serial 收集器更高效。虽然Serial收集器是基于串行回收,但是由于CPU不需要频繁地做任务切换,因此可以有效避免多线程交互过程中产生的一些额外开销。
  5. -XX:+UseParallelGC:使用ParNew收集器、-XX:ParallerGCThreads:限制线程数量,默认开启和CPU数据相同的线程数,例如-XX:ParallelGCThreads=4

Parallel 回收器

  1. Parallel收集器也称为吞吐量收集器
  2. 采用了复制算法、并行回收和STW机制
  3. Parallel收集器在JDK1.6时提供了用于执行老年代垃圾收集的Parallel old收集器,用来代替老年代的serial old收集器。
  4. Parallel Old收集器采用了标记-压缩算法,但同样也是基于并行回收和"Stop-The-World”机制,是jdk8的默认垃圾收集器
  5. 在程序吞吐量优先的应用场景中,Parallel收集器和Parallel old收集器的组合,在server模式下的内存回收性能很不错。

参数

  1. -XX:+UseParallelGC:指定年轻代用并行收集器
  2. -XX:+UseParallelOldGC:指定老年代使用并行收集器,1和2两个参数开启一个,另一个也会被开启
  3. -XX:ParallelGCThreads:设置年轻代并行收集器的线程数,最好和CPU数量相等
  4. -XX:+UseAdaptiveSizePolicy:设置Parallel收集器具有自适应调节策略
  5. -XX:MaxGCPauseMillis:设置垃圾收集器最大停顿时间,单位是毫秒
  6. -XX:GCTimeRatio:垃圾收集时间占总时间的比例,用于衡量吞吐量的大小。

CMS回收器(低延迟)

  1. CMS 收集器:此收集器适用于喜欢较短的垃圾收集暂停并且能够与垃圾收集共享处理器资源的应用程序。
  2. CMS采用标记-清除算法。
  3. 使用-XX:+UseConcMarkSweepGC启用 CMS 收集器
  4. 从 JDK 9 开始不推荐使用 CMS 收集器
  5. 与其他可用的收集器类似,CMS 收集器是分代的;因此,次要和主要收集都会发生。CMS 收集器尝试通过使用单独的垃圾收集器线程在应用程序线程的执行的同时跟踪可到达的对象来减少由于主要收集而导致的暂停时间。
  6. 在每个主要的收集周期中,CMS 收集器会在收集开始时暂停所有应用程序线程一小段时间,并在收集中间再次暂停。第二次停顿往往是两次停顿中较长的一次。多个线程在两次暂停期间执行收集工作。一个或多个垃圾收集器线程完成剩余的收集工作(包括大部分活动对象的跟踪和不可达对象的清扫)。次要收集可以与正在进行的主要循环交错,并以类似于并行收集器的方式完成(特别是,应用程序线程在次要收集期间停止)。
  7. 会产生内存碎片,为新对象分配内存空间只能选择空闲列表执行内存分配

CMS工作原理

  1. 初始标记阶段:CMS 收集器在并发收集周期中两次暂停应用程序。第一个暂停是将可从根直接访问的对象(例如,来自应用程序线程堆栈和寄存器的对象引用、静态对象等)和堆中的其他地方(例如,年轻代)标记为活动对象
  2. 并发标记阶段:从Gc Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。
  3. 重新标记阶段:第一次停顿被称为 initial mark pause. 第二次暂停出现在并发跟踪阶段结束时,它会在 CMS 收集器完成跟踪该对象后查找由于应用程序线程更新对象中的引用而被并发跟踪遗漏的对象。这第二次停顿被称为remark pause.
  4. 此阶段清理删除掉标记阶段判断的已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的

CMS优点

并发收集、低延迟

CMS缺点

  1. 会产生内存碎片,导致并发清除后,用户线程可用的空间不足。在无法分配大对象的情况下,不得不提前触发Full GC。
  2. CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
  3. CMS收集器无法处理浮动垃圾

CMS参数

  1. -XX:+UseConcMarkSweepGC:指定使用CMS执行垃圾回收,开启参数后会自动将-XX:UseParNewGC打开,即ParNew(Young区) + CMS(Old区) + Serial Old组合
  2. 如果老年代的占用率超过初始占用率(老年代的百分比),并发收集也会开始。此初始占用阈值的默认值约为 92%,但该值可能会因版本而异。可以使用命令行选项手动调整此值-XX:CMSInitiatingOccupancyFraction=,其中是旧代大小的整数百分比(0 到 100。

六种垃圾回收器小结

  1. 如果你想要最小化地使用内存和并行开销,请选Serial GC;
  2. 如果你想要最大化应用程序的吞吐量,请选Parallel GC;
  3. 如果你想要最小化cc的中断或停顿时间,请选CMS GC。

Garbage-First (G1)

Garbage-First简介

  1. Garbage-First (G1) 垃圾收集器针对具有大量内存的多处理器机器。它试图以高概率满足垃圾收集暂停时间目标,同时在几乎不需要配置的情况下实现高吞吐量。G1 旨在使用当前的目标应用程序和环境在延迟和吞吐量之间提供最佳平衡。
  2. G1 替换了并发标记扫描 (CMS) 收集器。它也是默认收集器。
  3. Garbage-First 垃圾收集器是默认收集器,不必执行任何其他操作。可以通过 -XX:+UseG1GC在命令行上提供来显式启用它
  4. G1 是一个分代的、增量的、并行的、主要是并发的、STW和疏散垃圾收集器,它监视每个STW暂停中的暂停时间目标。与其他收集器类似,G1 将堆分成(虚拟)年轻代和年老代。空间回收工作集中在最有效的年轻代上,偶尔在老年代进行空间回收
  5. 为了使空间回收的STW暂停较短,G1 逐步并行地执行空间回收。G1 通过跟踪有关先前应用程序行为和垃圾收集暂停的信息来构建相关成本模型,从而实现可预测性。它使用此信息来确定在暂停中完成的工作的大小。例如,G1 首先回收效率最高的区域(即大部分被垃圾填满的区域,因此得名)中的空间。
  6. G1收集器不是实时收集器。它试图在更长的时间内以高概率满足设定的暂停时间目标,但对于给定的暂停并不总是绝对确定。
  7. G1 将堆划分为一组大小相等的堆区域,每个区域都是连续的虚拟内存范围,如图 9-1 所示。区域是内存分配和内存回收的单位。在任何给定时间,这些区域中的每一个都可以是空的(浅灰色),或者分配给特定的一代,无论是年轻的还是年老的。当内存请求到来时,内存管理器会分发空闲区域。内存管理器将它们分配给一代,然后将它们作为可用空间返回给应用程序,应用程序可以在其中分配自己。
  8. 图9-1说明:年轻代包含 eden 区域(红色)和幸存者区域(红色带“S”)。这些区域提供与其他收集器中的相应连续空间相同的功能,不同之处在于在 G1 中,这些区域通常以非连续模式布置在内存中。老区(浅蓝色)组成老年代。对于跨越多个区域的对象,老年代区域可能是巨大的(带有“H”的浅蓝色)。
  9. 空间整合:G1将内存划分为一个个的region。内存的回收是以region作为基本单位的。Region之间是复制算法,但整体上实际可看作是标记-压缩(Mark-Compact算法,两种算法都可以避免内存碎片。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次 GC。尤其是当Java堆非常大的时候,G1的优势更加明显。
  10. 不超过几百毫秒的可预测的暂停时间目标,避免长时间的垃圾收集暂停

垃圾回收周期

  1. 主要过程
    1.1 年轻代Gc(Young GC)
    1.2 老年代并发标记过程(Concurrent Marking)
    1.3 混合回收(Mixed Gc)
  2. 应用程序分配内存,当年轻代的Eden区用尽时开始年轻代回收过程;G1的年轻代收集阶段是一个并行的独占式收集器。在年轻代回收期,G1 Gc暂停所有应用程序线程,启动多线程执行年轻代回收。然后从年轻代区间移动存活对象到Survivor区间或者老年区间,也有可能是两个区间都会涉及。
  3. 当堆内存使用达到一定值(默认45%)时,开始老年代并发标记过程。
  4. 标记完成马上开始混合回收过程。对于一个混合回收期,G1 Gc从老年区间移动存活对象到空闲区间,这些空闲区间也就成为了老年代的一部分。和年轻代不同,老年代的G1回收器和其他cc不同,G1的老年代回收器不需要整个老年代被回收,一次只需要扫描/回收一小部分老年代的Region就可以了。同时,这个老年代Region是和年轻代一起被回收的。

Remembered Set

  1. 无论G1还是其他分代收集器,JVM都是使用Remembered Set来避免全局扫描:
  2. 每个Region都有一个对应的Remembered Set;
  3. 每次Reference类型数据写操作时,都会产生一个Write Barrier暂时中断操作;
  4. 然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的Region(其他收集器:检查老年代对象是否引用了新生代对象)﹔
  5. 如果不同,通过cardTable把相关引用信息记录到引用指向对象的所在Region对应的Remembered set中;
  6. 当进行垃圾收集时,在Gc根节点的枚举范围加入Remembered Set;就可以保证不进行全局扫描,也不会有遗漏。
  7. JVM启动时,G1先准备好Eden区,程序在运行过程中不断创建对象到Eden区,当Eden空间耗尽时,G1会启动一次年轻代垃圾回收过程。
  8. 年轻代垃圾回收只会回收Eden区和survivor区。
  9. YGC时,首先G1停止应用程序的执行(Stop-The-World),G1创建回收集(Collection Set),回收集是指需要被回收的内存分段的集合,年轻代回收过程的回收集包含年轻代Eden区和Survivor区所有的内存分段。

G1回收过程

1. 年轻代GC

  1. 扫描根
    根是指static变量指向的对象,正在执行的方法调用链条上的局部变量等。根引用连同RSet记录的外部引用作为扫描存活对象的入口。
  2. 更新RSet
    处理dirty card queue中的card,更新RSet。此阶段完成后,RSet可以准确的反映老年代对所在的内存分段中对象的引用。
    card:对于应用程序的引用赋值语句object.field=object , JVM会在之前和之后执行特殊的操作以在dirty card queue中入队一个保存了对象引用信息的card。在年轻代回收的时候,G1会对Dirty Card Queue中所有的card进行处理,以更新RSet,保证RSet实时准确的反映引用关系。
  3. 处理RSet
    识别被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活的对象。
  4. 复制对象
    此阶段,对象树被遍历,Eden区内存段中存活的对象会被复制到survivor区中空的内存分段Survivor区内存段中存活的对象如果年龄未达阈值,年龄会加1,达到阀值会被会被复制到old区中空的内存分段。如果Survivor空间不够,Eden空间的部分数据会直接晋升到老年代空间
  5. 处理引用
    处理Soft,weak,Phantom,Final,3NI weak 等引用。最终Eden空间的数据为空,Gc停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。

2. 并发标记过程

1.初始标记阶段:标记从根节点直接可达的对象。这个阶段是STW的,并且会触发一次年轻代GC。
2.根区域扫描(Root Region Scanning) : 61 Gc扫描survivor区直接可达的老年代区域对象,并标记被引用的对象。这一过程必须在young Gc之前完成。
3. 并发标记(Concurrent Marking):在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young Gc中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
4、再次标记(Remark):由于应用程序持续进行,需要修正上一次的标记结果。是STw的。G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
5.独占清理(cleanup,STw):计算各个区域的存活对象和6c回收比例,并进行排序,识别可以混合回收的区域。为下阶段做铺垫。是STW的。这个阶段并不会实际上去做垃圾的收集
6.并发清理阶段:识别并清理完全空闲的区域。

3. 混合回收

  1. 当越来越多的对象晋升到老年代oldregion时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed Gc,该算法并不是一个oldGc,除了回收整个Young Region,还会回收一部分的o1d Region。
  2. 并发标记结束以后,老年代中百分百为垃圾的内存分段被回收了,部分为垃圾的内存分段被计算了出来。默认情况下,这些老年代的内存分段会分8次(可以通过-XX:G1MixedGccountTarget设置)被回收。
  3. 混合回收的回收集(collection Set)包括八分之一的老年代内存分段,Eden区内存分段,Survivor区内存分段。混合回收的算法和年轻代回收的算法完全一样,只是回收集多了老年代的内存分段。
  4. 由于老年代中的内存分段默认分8次回收,G1会优先回收垃圾多的内存分段。垃圾占内存分段比例越高的,越会被先回收。并且有一个阈值会决定内存分段是否被回收,-XX:G1MixedGcLiveThresholdPercent,默认为65%,意思是垃圾占内存分段比例要达到65%才会被回收。如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会花费更多的时间。
  5. 混合回收并不一定要进行8次。有一个阈值-XX:G1HeapwastePercent,默认值为10%,意思是允许整个堆内存中有1e%的空间被浪费,意味着如果发现可以回收的垃圾占堆内存的比例低于10%,则不再进行混合回收。因为Gc会花费很多的时间但是回收到的内存却很少。

ZGC

posted @ 2021-12-10 15:43  翻蹄亮掌一皮鞋  阅读(140)  评论(0)    收藏  举报