jvm垃圾回收及内存分配

0.垃圾收集器的相关JVM参数

-XX:+UseSerialGC // 指定新生代和老年代使用串行化垃圾收集器
-XX:+UseParNewGC // 指定新生代使用ParNew收集器,老年代使用串行化收集器
-XX:ParallelGCThreads=5 // 指定ParNew回收器工作时的线程数量,最好跟CPU数量相当,默认为CPU数量,当 CPU 数量大于8个时,数量为 3+((5*CPU个数)/8)
—XX:+UseParallelGC // 指定新生代使用ParallelGC收集器,老年代使用串行化收集器
-XX:+UseParallelOldGC // 指定新生代使用ParallelGC,老年代使用ParallelOldGC收集器
-XX:MaxGCPauseMillis=X // 指定最大STW时间,值大于0,ParallelGC 在工作时,会调整 Java 堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMillis 以内。如果希望减少停顿时间,而把这个值设得很小,为了达到预期的停顿时间,虚拟机可能会使用一个较小的堆(一个小堆比一个大堆回收快),而这将导致垃圾回收变得很频繁,从而增加了垃圾回收总时间,降低了吞吐量。    
-XX:GCTimeRatio=X // 设置系统吞吐量大小,值为0-100范围,如果GCTimeRatio的值为n,那么系统将花费不超过1/(1+n)的时间用于垃圾收集。比如GCTimeRatio 等于 19(默认值),则系统用于垃圾收集的时间不超过 1/(1+19)=5%。默认情况下,它的取值是 99,即不超过 1/(1+99)=1%的时间用于垃圾收集。
-XX:+UseAdaptiveSizePolicy // 开启GC自适应策略。新生代,eden,survivor等相关信息都会被调整
-XX:+UseConcMarkSweepGC // 新生代使用 ParNew 回收器,老年代使用 CMS,默认线程数量是 (ParallelGCThreads + 3)/4
-XX:-CMSPrecleaningEnabled // CMS中,不进行预清理操作
-XX:ConcGCThreads // 执行并发线程数量,也可以用于控制CMS收集器
-XX:ParallelCMSThreads // 指定CMS并发线程数量
-XX:CMSInitiatingOccupancyFraction=X // 执行CMS回收阈值,默认是68 当老年代使用率达到68%时,会进行CMS回收
-XX:+UseCMSCompactAtFullCollection:// 指定在CMS回收完成后,是否开启内存碎片整理
-XX:CMSFullGCsBeforeCompaction=X // 指定进行多少次FULL GC后,进行内存整理
-XX:+CMSClassUnloadingEnabled // 开启CMS回收永久区/MetaSpace
-XX:+UseG1GC // 使用G1垃圾收集器 

1.java垃圾回收器种类

  • 串行垃圾回收器
  • 并行垃圾回收器
  • CMS回收器
  • G1回收器
  • ...

2.串行回收器

  • 串行回收器是指使用单线程进行垃圾回收的回收器。每次回收时,串行回收器只有一个工作线程
  • 串行回收器可以在新生代和老年代使用,根据作用于不同的堆空间,分为新生代串行回收器和老年代串行回收器。

串行垃圾收集器的特点:

第一,它仅仅使用单线程进行垃圾回收

第二 ,它是独占式的垃圾回收

在串行收集器进行垃圾回收时 Java 应用程序中的线程都需要暂停 ,等待垃圾回收的完成

新生代使用复制算法,老年代使用标记压缩算法,因为老年代存活对象比较多,因此每次老年代GC时时间会比较长,STW也会比较长

image

SerialGC输出格式

[GC (Allocation Failure) [DefNew: 960K->63K(960K), 0.0017692 secs] 1428K->778K(1048512K), 0.0018002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [Tenured: 1047551K->1047551K(1047552K), 0.7139298 secs] 1048511K->1048508K(1048512K), [Metaspace: 3795K->3795K(1056768K)], 0.7139578 secs] [Times: user=0.70 sys=0.00, real=0.71 secs] 

ParNewGC输出格式

[GC (Allocation Failure) [ParNew: 959K->64K(960K), 0.0008930 secs] 37175K->37255K(1048512K), 0.0009207 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [Tenured: 1047551K->1047551K(1047552K), 0.6854967 secs] 1048511K->1048480K(1048512K), [Metaspace: 3817K->3817K(1056768K)], 0.6855433 secs] [Times: user=0.67 sys=0.02, real=0.68 secs] 

ParallelGC输出格式

[GC (Allocation Failure) [PSYoungGen: 992K->512K(1024K)] 40972K->40844K(1048064K), 0.0005923 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 512K->0K(1024K)] [ParOldGen: 1044940K->944464K(1047040K)] 1045452K->944464K(1048064K), [Metaspace: 3788K->3788K(1056768K)], 0.6676265 secs] [Times: user=4.74 sys=0.03, real=0.67 secs] 

CMS GC输出格式

[GC (CMS Initial Mark) [1 CMS-initial-mark: 1047551K(1047552K)] 1048511K(1048512K), 0.0002479 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [CMS[CMS-concurrent-mark: 0.266/0.267 secs] [Times: user=0.70 sys=0.02, real=0.27 secs] 
 (concurrent mode failure): 1047551K->1047551K(1047552K), 0.9103748 secs] 1048511K->1048507K(1048512K), [Metaspace: 3789K->3789K(1056768K)], 0.9104161 secs] [Times: user=1.36 sys=0.02, real=0.91 secs] 

G1 GC输出格式

[GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0021869 secs]
   [Parallel Time: 1.3 ms, GC Workers: 10]
      [GC Worker Start (ms): Min: 2134.1, Avg: 2134.1, Max: 2134.2, Diff: 0.1]
      [Ext Root Scanning (ms): Min: 0.2, Avg: 0.3, Max: 0.4, Diff: 0.2, Sum: 2.9]
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.6, Avg: 0.7, Max: 0.7, Diff: 0.1, Sum: 6.5]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Termination Attempts: Min: 1, Avg: 8.5, Max: 13, Diff: 12, Sum: 85]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.0, Sum: 0.4]
      [GC Worker Total (ms): Min: 0.9, Avg: 1.0, Max: 1.0, Diff: 0.1, Sum: 9.9]
      [GC Worker End (ms): Min: 2135.1, Avg: 2135.1, Max: 2135.1, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.1 ms]
   [Other: 0.8 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.2 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 3072.0K(10.0M)->0.0B(8192.0K) Survivors: 0.0B->2048.0K Heap: 8192.0K(20.0M)->6304.0K(20.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 

3. 并行回收器

  • 并行回收器在串行回收器的基础上做了改进,它使用多个线程同时进行垃圾回收。对于并行能力强的计算机,可以有效缩短垃圾回收所需的实际时间。

3.1 ParNew回收器

  • ParNew 回收器是一个工作在新生代的垃圾收集器。它将串行回收器多线程化,回收策略、算法以及参数和新生代串行回收器一样。
  • ParNew 回收器也是独占式的回收器,在收集过程中,应用程序会全部暂停。但由于并行回收器使用多线程进行垃圾回收,因此,在并发能力比较强的CPU上,它产生的停顿时间要短于串行回收器,而在单CPU或者并发能力较弱的系统中,并行回收器的效果不会比串行回收器好,由于多线程的压力,它的实际表现很可能比串行回收器差。

image

3.2 ParallelGC 回收器

  • ParallelGC 回收器也是使用复制算法的收集器。
  • 和ParNew 回收器一样,都是多线程、独占式的收集器。但ParallelGC 回收器有非常关注系统的吞吐量。
  • ParallelGC支持一种自适应的GC 调节策略。使用-XX:+UseAdaptiveSizePolicy 可以打开自适应 GC 策略。在这种模式下,新生代的大小、eden 和 survivior 的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。在手工调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标吞吐量(GCTimeRatio)和停顿时间(MaxGCPauseMillis),让虚拟机自己完成调优工作。

3.3 ParallelOldGC 回收器

  • 老年代 ParallelOldGC 回收器也是一种多线程并发的收集器。

  • 和新生代 ParallelGC 回收器样,它也是一种关注吞吐量的收集器。

  • 和ParallelGC 新生代回收器搭配使用。

  • ParallelOldGC 回收器使用标记压缩算法

image

3.4 CMS 回收器

  • 与ParallelGC 和ParallelOldGC 不同,CMS 回收器主要关注于系统停顿时间。
  • CMS是Concurrent Mark Sweep的缩写,意为并发标记清除,
  • 它使用的是标记清除算法,同时它又是一个使用多线程并行回收的垃圾回收器。

CMS 主要工作步骤

CMS工作时,主要步骤有:

1. 初始标记
2. 并发标记
3. 预清理
4. 重新标记
5. 并发清除和并发重置

其中初始标记和重新标记是独占系统资源的,而预清理、并发标记、并发清除和并发重置是可以和用户线程一起执行的。CSM 收集不是独占式的,它可以在应用程序运行过程中进行垃圾回收。

image

CMS说明

  1. 初始标记、并发标记和重新标记都是为了标记出需要回收的对象。

  2. 并发清理则是在标记完成后,正式回收垃圾对象。

  3. 并发重置是指在垃圾回收完成后,重新初始化CMS数据结构和数据,为下一次垃圾回收做好准备。

  4. 并发标记、并发清理和并发重置都是可以和应用程序线程一起执行的。

  • 预清理是并发的,除了为正式清理做准备和检查以外,预清理还会尝试控制一次停顿时间。

  • 由于重新标记是独占CPU的,如果新生代 GC 发生后,立即触发一次重新标记,那么一次停顿时间可能很长。预清理为了避免这种情况,预处理时,会刻意等待一次新生代GC的发生,然后根据历史性能数据预测下一次新生代GC可能发生的时间,然后在当前时间和预测时间的中间时刻,进行重新标记。这样,从最大程度上避免新生代 GC和重新标记重合,尽可能减少一次停顿时间。

  • CMS回收器不是独占式的回收器

  • 在CMS回收过程中,应用程序仍然在不停地工作,会不断地产生垃圾。这些新生成的垃圾在当前CMS回收过程中是无法清除的。所以在CMS回收过程中,还应该确保应用程序有足够的内存可用。因此,CMS回收器不会等待堆内存饱和时才进行垃圾回收,而是当堆内存使用率达到某一阈值时便开始进行回收,以确保应用程序在CMS工作过程中,依然有足够的空间支持应用程序运行。

  • 回收阈值可以使用-XX:CMSInitiatingOccupancyFraction来指定,默认是68。即当老年代的空间使用率达到68%时,会执行一次CMS回收。如果应用程序的内存使用率增长很快,在CMS的执行过程中,已经出现了内存不足的情况,此时,CMS回收就会失败,虚拟机将启动老年代串行收集器进行垃圾回收。如果这样,应用程序将完全中断,直到垃圾回收完成,这时,应用程序的停顿时间可能会较长。

3.5 G1回收器

  • G1回收器(Garbage-First)是在JDK 1.7中正式使用的全新的垃圾回收器
  • 目的是为了取代 CMS 回收器。
  • G1回收器拥有独特的垃圾回收策略。
    • 从分代上看,G1依然属于分代垃圾回收器,它会区分年轻代和老年代,依然有eden区和 survivor 区,
    • 从堆的结构上看,它并不要求整个 eden 区、年轻代或者老年代都连续。
    • 使用了分区算法。
  1. 并行性: G1在回收期间,可以由多个GC线程同时工作,有效利用多核计算能力。
  2. 并发性: G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此一般来说,不会在整个回收期间完全阻塞应用程序。
  3. 分代GC: G1依然是一个分代收集器,但是和之前回收器不同,它同时兼顾年轻代和老年代。对比其他回收器,它们或者工作在年轻代,或者工作在老年代。因此,这里是一个很大的不同。
  4. 空间整理: G1在回收过程中,会进行适当的对象移动,不像CMS,只是简单地标记清理对象,在若于次 GC后,CMS必须进行一次碎片整理。而G1不同,它每次回收都会有效地复制对象,减少空间碎片。
  5. 可预见性: 由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿也能得到较好的控制。

G1垃圾收集器的4个阶段

  • 新生代GC: 回收eden和survivor区
  • 并发标记周期:
  • 混合收集
  • 如果需要,可能会进行 FullGC

3.5.1 新生代GC

新生代 GC 的主要工作:

  1. 回收 eden 区和 survivor 区。

  2. 一旦 eden 区被占满,新生代 GC 就会启动。

  3. 新生代 GC 只处理 eden 和 survivor 区,回收后,所有的 eden 区都应该被清空,而 survivor 区会被收集一部分数据,但是应该至少仍然存在一个 survivor 区

  4. 老年代的区域增多,因为部分 survivor 区或者eden 区的对象可能会晋升到老年代。

image

3.5.2 G1的并发标记周期

  1. 初始标记: 标记从根节点直接可达的对象。这个阶段会伴随一次新生代GC,它是会产生全局停顿的,应用程序线程在这个阶段必须停止执行。
  2. 根区域扫描: 由于初始标记必然会伴随一次新生代GC,所以在初始化标记后,eden 被清空,并且存活对象被移入survivor 区。在这个阶段,将扫描由 survivor 区直接可达的老年代区域,并标记这些直接可达的对象。这个过程是可以和应用程序并发执行的。但是根区域扫描不能和新生代 GC 同时执行(因为根区域扫描依赖survivor 区的对象,而新生代 GC 会修改这个区域),因此如果恰巧在此时需要进行新生代GC,GC 就需要等待根区域扫描结束后才能进行,如果发生这种情况,这次新生代GC的时间就会延长
  3. 并发标记: 和CMS类似,并发标记将会扫描并查找整个堆的存活对象,并做好标记。这是一个并发的过程,并且这个过程可以被一次新生代 GC 打断。
  4. 重新标记: 和CMS一样,重新标记也是会产生应用程序停顿的。由于在并发标记过程中,应用程序依然在运行,因此标记结果可能需要进行修正,所以在此对上一次的标记结果进行补充。在G1中,这个过程使用SATB(Snapshot-At-The-Beginning)算法完成即 G1会在标记之初为存活对象创建一个快照,这个快照有助于加速重新标记的速度。
  5. 独占清理:这个阶段是会引起停顿的。它将计算各个区域的存活对象和GC回收比例并进行排序,识别可供混合回收的区域。在这个阶段,还会更新记忆集(RemeberedSet)。该阶段给出了需要被混合回收的区域并进行了标记,在混合回收阶段,需要这些信息。
  6. 并发清理阶段: 这里会识别并清理完全空闲的区域。它是并发的清理,不会引起停顿。

image

image

  1. 初始化标记,伴随一次新生代GC,eden被清空,存活对象复制到survivor

image

  1. 根区域并发扫描,不能被新生代GC打断,期间会产生STW

image

  1. 并发标记,可以被新生代GC 打断,下面的日志显示了一次并发标记被3 次新生代 GC 打断。

image

  1. 重新标记,会引起全局STW

image

  1. 独占清理,会重新计算各个区域的存活对象,并以此可以得到每个区域进行GC的回收比。

image

  1. 并发清理,是并发执行的,会根据独占清理阶段计算得出的每个区域的存活对象数量,直接回收已经不包含存活对象的区域。

image

3.5.3 混合回收

  • 并发标记周期中,回收垃圾的比例是相当低的。

  • 并发标记周期后,G1已经明确知道哪些区域含有比较多的垃圾对象,在混合回收阶段,就可以专门针对这些区域进行回收。

  • G1会优先回收垃圾比例较高的区域,因为回收这些区域的性价比也比较高。而这也正是G1名字的由来。G1全称为Garbage First Garbage Collector,直译为垃圾优先的垃圾回收器,这里的垃圾优先(Garbage First)指的就是回收时优先选取垃圾比例最高的区域。

    混合回收,在这个阶段,既会执行正常的年轻代GC,又会选取一些被标记的老年代区域进行回收,它同时处理了新生代和老年代。

image

  • 因为新生代GC的原因,eden 区域必然被清空,此外,有两块被标记位G的垃圾比例最高的区域被清理。被清理区域中的存活对象会被移动到其他区域,这样的好处是可以减少空间碎片。

混合GC日志

image
.png)

3.5.4 G1日志解析

  1. 日志第一行

image

表示在应用程序开启1.619秒时发生了一次新生代GC,这是在初始标记时发生的,耗时0.038 秒,意味着应用程序至少暂停了0.038秒。

  1. 后续并行时间

image

表示所有 GC 线程总的花费时间,这里为38毫秒

  1. 每一个 GC 线程的执行情况

image

GC 线程的执行情况,一共4个GC线程,它们都在 1619.3 秒时启动。同时给出了这几个启动数据的统计值,如平均(Avg),最小(Min)、最大(Max)和差值(Diff),Diff 表示最大值和最小值的差。

  1. 根扫描的耗时

image

在根扫描时(全局变量、系统数据字典、线程栈等),每一个GC线程的耗时,这里分配消耗了 0.3、0.3、0.2、0.2秒时间,后一行给出这些耗时的统计数据。

  1. 更新记忆集(Remember Set)的耗时

image
.png)

记忆集是 G1中维护的一个数据结构,简称 RS。每一个 G1 区域都有一个 RS 与之关联。由于 G1 回收时,是按照区域回收的,比如在回收区域 A的对象时,很可能并不回收区域B的对象此时,为了回收区域 A 的对象,要扫描区域 B 甚至是整个堆来判定区域 A 中哪些对象不可达,这样做的代价显然很大。因此,G1在区域A的RS中,记录了在区域A中被其他区域引用的对象,这样在回收区域A 时,只要将 RS 视为区域A 根集的一部分即可,从而可以避免做整个堆的扫描。由于系统在运行过程中,对象之间的引用关系是可能时刻变化的,因此为了更高效地跟踪这些引用关系,会将这些变化记录在 Update Bufers 中。这里的 Processed Buffers 指的就是处理这个 Update Buffers数据。这里给出的4个时间和也是4个GC 线程的耗时,以及它们的统计数据。从这个日志中可以看到,更新 RS 时,分别耗时 5.7、5.4、28、5.3 毫秒,平均耗时 11.1 毫秒。

  1. 扫描 RS 的时间

image

  1. 在正式回收时,G1会对被回收区域的对象进行疏散,即将存活对象放置在其他区域中,因此需要进行对象的复制。

posted on 2024-05-14 16:06  ccblblog  阅读(7)  评论(0编辑  收藏  举报

导航