文章目录
  1. JVM学习02-GC算法与种类
  2. 1. GC 简介
  3. 2. GC 算法
    1. 2.1 引用计数法
    2. 2.2 标记-清除
    3. 2.3 标记-压缩
    4. 2.4 复制算法
    5. 2.5 增量算法
    6. 2.6 总结
  4. 3. 分代思想
  5. 4. 对象的可触及性
  6. 5. Stop-The-World (STW) 现象
  7. 6. 垃圾回收器的种类
    1. 6.1 新生代串行收集器
    2. 6.2 老年代串行收集器
    3. 6.3 并行收集器
    4. 6.4 新生代并行回收收集器
    5. 6.5 老年代并行回收收集器
    6. 6.6 CMS收集器
    7. 6.7 G1收集器
  8. 7. GC 相关的参数

[TOC]

JVM学习02-GC算法与种类

1. GC 简介

GC(Garbage Collection) 是垃圾收集的简写,GC机制是java中一个比较重要的概念。java的内存管理提供了内存的分配和释放,内存处理是程序编写人员很容易出错的地方,忘记或错误的内存回收很容易导致系统的不稳定,甚至瘫痪。java的GC机制可以很好的检测对象是否超过作用域而可以达到回收的要求,从而实现自动回收垃圾对象的释放内存的目的。

其实早在很久以前(1960)就已经有了GC的概念,只是java借用这个优秀的思想,在java内存模型中,GC的工作区域主要是在堆区域和方法区,大部分都是在堆区域中。   

2. GC 算法

2.1 引用计数法

首先呢,java并没有采用引用计数法作为GC算法,因为它有明显的缺陷。

引用计数法实现比较简单:对于一个对象A来说,只要有任何一个对象或者引用指向了A,那么A的计数器就加1,当引用失效时,引用计数器就减1,最后只要对象A的引用计数器的值为0,那么A就不可能再被使用,就可被视为垃圾。

引用计数法存在明显的缺陷,第一由于不断引用和去除引用伴随着加减法,影响性能。第二最大是问题就是很难解决循环引用的问题。

这样就会存在很多的对象无法被回收,所以JVM压根没有使用引用计数法作为GC算法。

2.2 标记-清除

  标记清除(Mark-Sweep)算法是现代垃圾回收算法的基础。顾名思义,标记清除算法将垃圾清除分为两个阶段:标记和清除

  • 标记:通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用即不可达的对象。
  • 清除:清除所有未被标记的对象。

2.3 标记-压缩

标记压缩(Mark-Compact)算法适用于存活对象比较多的场合,比如老年代。它在标记-清除算法基础上做了一些优化,标记压缩算法和标记清除算法一样,首先通过根节点标记可达的对象,然后还将所有可达的对象(存活的对象)压缩到内存一端,然后清理边界外所有空间。

  • 标记:通过根节点标记可达的对象。
  • 压缩:将存活的对象整理到内存一端,清理边界外所有空间。

2.4 复制算法

与标记清除算法相比,复制(Copying)算法相对是一个比较高效的算法,由于涉及到存活对象的赋值,所以复制算法不适合存活对象比较多的场合(如不适合老年代)。复制算法的思想大致如下:将原有的内存分为两块空间,每次只使用其中一块,在垃圾回收的时候,将正在使用内存中存活的对象复制到未使用的内存块中,之后清除正在使用内存中的所有对象,然后交换两个内存的角色完成垃圾回收。

2.5 增量算法

还有一种算法是增量算法(直接摘抄了):增量算法的基本思想是,如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。每次,垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复,直到垃圾收集完成。使用这种方式,由于在垃圾回收过程中,间断性地还执行了应用程序代码,所以能减少系统的停顿时间。但是,因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。

2.6 总结

  1. 首先是引用计数法,这个缺陷比较明显,JVM也没有采用,不做讨论。
  2. 标记清除和标记压缩相比,前者产生大量不连续的内存碎片。后者虽然不产内存碎片,但是在前者的基础上还进行对象的压缩(整理),所以成本相对较高。且两者都会进行标记和清除,效率不是很高。
  3. 复制算法虽然比较简单高效,且不会产生内存碎片,但是明显浪费了大量的内存空间

3. 分代思想

根据GC算法的总结可以知道,结合不同算法的优点才能规划出一套比较可行的方案,所谓没有最好只有更好。

前面JVM内存模型中有说到将JAVA堆分为新生代老年代,然后又将新生代细分为eden spacesurvivor space(分为from和to或者s0和s1),然后根据对象的存活周期将短命对象归为新生代,长命对象归为老年代。

所以根据不同代的特点,在对象存活比较少的新生代采用复制算法,在老年代采用标记清除标记压缩算法。

图中,在进行垃圾回收的时候,存活对象如何存放有很多种可能。

    1. 左边绿色的箭头表示在垃圾回收的时候,第一种年龄比较大的对象会存放在老年代中;第二种情况就是一些比较大的对象无法放到 survivor 空间中,那么此时大对象也被存放到老年代中,所以有时候也称老年代是一个担保空间。
    1. 左边红色和黄色的箭头就是进行正常的复制算法,垃圾回收结束就是右图的样子。
    1. 有的时候查看GC日志的时候,新生代的垃圾回收一般称为minor gc。老年代由于区域比较大且存活对象很多,生命周期很长,所以gc时候会比较长,通常称为full gc。

4. 对象的可触及性

对象的可触及性顾名思义就是从根节点可以标记的对象。大家一般情况会认为对象要么是可触及的,要么是不可触及的,其实中间还存在一个可复活性。

  • 可触及性:从根节点可以触及的对象

  • 可复活性:一旦所有引用被释放,对象进入可复活状态,因为在 finalize() 方法后对象可能变得可触及

  • 不可触及性:finalize() 方法之后,对象可能进入不可触及状态。不可触及的对象不能复活,然后进入可回收的状态。

这里需要注意的一点是 finalize() 方法只会被执行一次,所以某个对象不能无限从可复活性到达可触及性。而且 finalize() 方法的执行优先级很低,何时出发GC并不确定,finalize()方法的调用也变得不确定。

更详细参照:《finalize 总结》

5. Stop-The-World (STW) 现象

STW现象是java的一种全局暂停的现象,所有Java代码停止运行(Native代码可以执行,但不能与JVM交互)。产生这种现象的原因多半是由于 GC 引起的。GC 为什么产生 STW 现象?这个以开 Party 打个比方:我们在开party 的时候会产生很多的垃圾,那么此时如果有人来清理垃圾,我们他在清理的时候,我们又不断产生垃圾,那么房间永远打扫不干净,所以唯一的办法就是我们停止手中的事情,直到房间打扫干净后再进行活动。
  
那么GC也是这个道理,当发生GC的时候,必然所有的工作线程会停止,那么此时就会产生java停顿的现象。
  
当发生STW现象的时候,如果时间短还好,如果时间特别长甚至几十分钟,服务器就会长时间得不到响应,那么就会带来比较大的危害。解决方法可以使用主备机的切换吧,具体不是很清楚,也不展开了。

6. 垃圾回收器的种类

6.1 新生代串行收集器

串行收集器是一个古老而稳定,经过长时间考验的垃圾收集器。在诸如单 CPU 处理器或者较小的应用内存等硬件平台不是特别优越的场合,它的性能表现可以超过并行回收器和并发回收器。但是有的时候会停顿很长时间,且是线程独占的。

新生代串行收集器采用了复制算法,当 JVM 在 Client 模式下运行时,它是默认的垃圾收集器。

在 HotSpot 虚拟机中,使用 -XX:+UseSerialGC 参数可以指定使用新生代串行收集器老年代串行收集器。此时老年代串行收集器采用的是标记-压缩算法

1
[GC 0.844: [DefNew: 17472K->2176K(19648K), 0.0188339 secs] 17472K->2375K(63360K), 0.0189186 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]

6.2 老年代串行收集器

老年代串行收集器和新生代串行收集器一样,老年代串行收集器采用的是标记-压缩算法,可以使用 -XX:+UseSerialGC 参数可以指定使用新生代串行收集器和老年代串行收集器。
图解和新生代串行收集器一样。

1
[Full GC 8.259: [Tenured: 43711K->40302K(43712K), 0.2960477 secs] 63350K->40302K(63360K), [Perm : 17836K->17836K(32768K)], 0.2961554 secs] [Times: user=0.28 sys=0.02, real=0.30 secs]

6.3 并行收集器

并行收集器工作在新生代,仅仅是将新生代串行收集器变成了多线程化了,它的回收策略、算法以及参数和串行回收器一样。
新生代依旧采用复制算法,老年代还是串行收集器(标记压缩算法)。GC依旧是线程独占的。
开启并行回收器可以使用参数 -XX:+UseParNewGC

多线程并不意味着GC一定会很快,且需要多核CPU的支持才会相对提高效率。
可以使用 -XX:ParallelGCThreads 限制线程数量

1
[GC 0.834: [ParNew: 13184K->1600K(14784K), 0.0092203 secs] 13184K->1921K(63936K), 0.0093401 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

6.4 新生代并行回收收集器

与 ParNew 类似,新生代并行回收收集器也是使用复制算法的收集器。从表面上看,它和并行收集器一样都是多线程、独占式的收集器。但是,并行回收收集器有一个重要的特点:它非常关注系统的吞吐量
使用 -XX:+UseParallelGC 设置 新生代使用Parallel收集器 + 老年代使用串行收集器
使用 -XX:+UseParallelOldGC 设置 新生代和老年代都使用Parallel收集器

1
[Full GC [PSYoungGen: 2682K->0K(19136K)] [ParOldGen: 28035K->30437K(43712K)] 30717K->30437K(62848K) [PSPermGen: 10943K->10928K(32768K)], 0.2902791 secs] [Times: user=1.44 sys=0.03, real=0.30 secs]

6.5 老年代并行回收收集器

老年代的并行收集器和新生代并行收集器一样,所线程且关注系统吞吐量,采用标记压缩的算法。
使用 -XX:+UseParallelOldGC 设置 新生代和老年代都使用Parallel收集器
图解和日志清单跟新生代并行收集器一样。

这里新生代和老年代的并行收集器和使用以下参数启动:

  • -XX:MaxGCPauseMills : 设置最大的停顿时间,单位是毫秒。GC会尽力保证回收的时间不超过吞吐量。
  • -XX:GCTimeRatio : 设置吞吐量的大小n,GC时间比[ 1/(1+n) ]。默认值为99,即最大允许1%的时间用于垃圾回收。

这两个参数本来就是矛盾的,如果将最大停顿时间设置越小,那么GC就会越频繁,从而降低整个系统的吞吐量。如果吞吐量设置越大,GC导致的停顿时间也会越长,所以有所矛盾。所以在实际中也可以采用自适应的GC调节策略,使用 -XX:+UseAdaptiveSizePolicy 可以打开自适应 GC 策略。在这种模式下,新生代的大小、eden 和 survivor 的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。在手工调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量 (GCTimeRatio) 和停顿时间 (MaxGCPauseMills),让虚拟机自己完成调优工作。

6.6 CMS收集器

CMS (Concurrent Mark Sweep)是并发标记清除的收集器,这里的并发表示可以和用户线程一起工作,且采用标记清除算法

CMS 收集器针是老年代收集器,新生代采用 ParNew 并行收集器。可以使用 -XX:+UseConcMarkSweepGC启动 CMS 收集器。

CMS 工作大致可分为如下几个过程:

  1. 初始标记:GC 线程独占,对根可以直接关联到的对象进行标记,速度比较快。
  2. 并发标记:GC 线程和用户线程一起执行,对所有的对象进行标记。
  3. 重新标记:由于上一步并发过程,用户线程可能还会生产出垃圾,所以GC 线程独占,在正式清理前重新做一次标记。
  4. 并发清除:GC 线程和用户线程一起执行,GC 回收垃圾对象。

1
2
3
4
5
6
7
8
9
10
1.662: [GC [1 CMS-initial-mark: 28122K(49152K)] 29959K(63936K), 0.0046877 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
1.666: [CMS-concurrent-mark-start]
1.699: [CMS-concurrent-mark: 0.033/0.033 secs] [Times: user=0.25 sys=0.00, real=0.03 secs]
1.699: [CMS-concurrent-preclean-start]
1.700: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
1.700: [GC[YG occupancy: 1837 K (14784 K)]1.700: [Rescan (parallel) , 0.0009330 secs]1.701: [weak refs processing, 0.0000180 secs] [1 CMS-remark: 28122K(49152K)] 29959K(63936K), 0.0010248 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
1.702: [CMS-concurrent-sweep-start]
1.739: [CMS-concurrent-sweep: 0.035/0.037 secs] [Times: user=0.11 sys=0.02, real=0.05 secs]
1.739: [CMS-concurrent-reset-start]
1.741: [CMS-concurrent-reset: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

CMS虽然是并发收集器,但是其实也会存在线程独占的情况导致全局停顿,只是降低了停顿的时间罢了。由于用户线程的运行过程中,GC的时候还需要分CPU去做垃圾回收,这样就会大大降低整体系统的反应速速。在清理阶段,由于和用户线程并发执行,还会产生新的垃圾,导致清理不彻底。甚至还可能会存在产生的垃圾使得CMS来不及清理,让可使用内存的容量迅速减小,直到内存预留不够,引起 concurrent mode failure 错误。

解决方案:

第一可以通过 -XX:CMSInitiatingOccupancyFraction 的值来设置触发CMS收集器的阀值。默认为68,即当老年代空间使用率达到68%的时候触发CMS回收。
第二当引起concurrent mode failure 错误的时候,JVM就会启动备用回收器 **老年代串行回收器** 作为GC回收器。

CMS收集器的一些参数:

  1. -XX:+UseCMSCompactAtFullCollection 参数将在进行一个Full GC之后进行一次内存压缩(整理),由于CMS采用的是标记清除算法。由于整理过程是线程独占的,所以可能引起的停顿时间较长。
  2. -XX:+CMSFullGCsBeforeCompaction 参数设置多少次Full GC后进行一次内存整理。
  3. -XX:ParallelCMSThreads 参数设置CMS线程的数量。

6.7 G1收集器

G1 (Garbage-First)是一款面向服务器的垃圾收集器, 主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征. 在Oracle JDK 7 update 4 及以上版本中得到完全支持, 专为以下应用程序设计:

  1. 可以像CMS收集器一样, GC操作与应用的线程一起并发执行
  2. 紧凑的空闲内存区间且没有很长的GC停顿时间.
  3. 需要可预测的GC暂停耗时.
  4. 不想牺牲太多吞吐量性能.
  5. 启动后不需要请求更大的Java堆.

G1的长期目标是取代CMS(Concurrent Mark-Sweep Collector, 并发标记-清除). 因为特性的不同使G1成为比CMS更好的解决方案. 一个区别是, G1是一款压缩型的收集器,G1通过有效的压缩完全避免了对细微空闲内存空间的分配, 不用依赖于regions,这不仅大大简化了收集器,而且还消除了潜在的内存碎片问题。除压缩以外,G1的垃圾收集停顿也比CMS容易估计,也允许用户自定义所希望的停顿参数(pause targets)。

详细参考博文:《G1垃圾收集器入门》

7. GC 相关的参数

1. 与串行回收器相关的参数

  • -XX:+UseSerialGC : 在新生代和老年代使用串行回收器。

  • -XX:+SuivivorRatio : 设置 eden 区大小和 survivor 区大小的比例。8表示 两个Survivor:eden=2:8,即一个Survivor占年轻代的1/10。

  • -XX:NewRatio : 新生代和老年代(不包含永久区)的比。4 表示 新生代:老年代=1:4,即年轻代占堆的1/5。

  • -XX:+PretenureSizeThreshold : 设置大对象直接进入老年代的阈值。当对象的大小超过这个值时,将直接在老年代分配。

  • -XX:MaxTenuringThreshold : 设置对象进入老年代的年龄的最大值。每一次 Minor GC 后,对象年龄就加 1。任何大于这个年龄的对象,一定会进入老年代。

2. 与并行 GC 相关的参数

  • -XX:+UseParNewGC : 在新生代使用并行收集器。

  • -XX:+UseParallelOldGC : 老年代使用并行回收收集器。

  • -XX:ParallelGCThreads:设置用于垃圾回收的线程数。通常情况下可以和 CPU 数量相等。但在 CPU 数量比较多的情况下,设置相对较小的数值也是合理的。

  • -XX:MaxGCPauseMills:设置最大垃圾收集停顿时间。它的值是一个大于 0 的整数。收集器在工作时,会调整 Java 堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMills 以内。

  • -XX:GCTimeRatio : 设置吞吐量大小,它的值是一个 0-100 之间的整数。假设 GCTimeRatio 的值为 n,那么系统将花费不超过 1/(1+n) 的时间用于垃圾收集。

  • -XX:+UseAdaptiveSizePolicy : 打开自适应 GC 策略。在这种模式下,新生代的大小,eden 和 survivor 的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。

3. 与 CMS 回收器相关的参数

  • -XX:+UseConcMarkSweepGC : 新生代使用并行收集器,老年代使用 CMS + 串行收集器(备用收集器)。

  • -XX:+ParallelCMSThreads : 设定 CMS 的线程数量。

  • -XX:+CMSInitiatingOccupancyFraction : 设置 CMS 收集器在老年代空间被使用多少后触发,默认为 68%。

  • -XX:+UseFullGCsBeforeCompaction : 设定进行多少次 CMS 垃圾回收后,进行一次内存压缩。

  • -XX:+CMSClassUnloadingEnabled : 允许对类元数据进行回收。

  • -XX:+CMSParallelRemarkEndable : 启用并行重标记。

  • -XX:CMSInitatingPermOccupancyFraction : 当永久区占用率达到这一百分比后,启动 CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)。

  • -XX:UseCMSInitatingOccupancyOnly : 表示只在到达阈值的时候,才进行 CMS 回收。

  • -XX:+CMSIncrementalMode : 使用增量模式,比较适合单 CPU。

4. 与 G1 回收器相关的参数

  • -XX:+UseG1GC:使用 G1 回收器。

  • -XX:+UnlockExperimentalVMOptions : 允许使用实验性参数。

  • -XX:+MaxGCPauseMills : 设置最大垃圾收集停顿时间。

  • -XX:+GCPauseIntervalMills : 设置停顿间隔时间。

5. 其他参数

  • -XX:+PrintGCDetails : 打开显示GC日志的开关。

  • -XX:+DisableExplicitGC : 禁用显示 GC。

  • -Xloggc:Xxx.log : 设置GC的log位置和名称。

  • -XX:+HeapDumpOnOutOfMemoryError : 当堆内存移除出错的时候显示最后的GC日志。

  • -XX:HeapDumpPath=/var/log/gc/oom-${your-app-name}.hprof: dump的路径

  • -Xmx 和 –Xms : 设置堆内存的最大允许值和最小值,如-Xmx32M -Xms32M

  • -XX:PermSize 和 -XX:MaxPermSize : 设置永久代的最小初始值和最大允许值 如-XX:PermSize=64MB 和 -XX:MaxPermSize=256M。

  • -XX:MaxPermSize缺省值和client/server选项相关,-server选项下默认MaxPermSize为64m,-client选项下默认MaxPermSize为32m。

  • -Xmn : 设置新生代大小 , 如 -Xmn16M

文章目录
  1. JVM学习02-GC算法与种类
  2. 1. GC 简介
  3. 2. GC 算法
    1. 2.1 引用计数法
    2. 2.2 标记-清除
    3. 2.3 标记-压缩
    4. 2.4 复制算法
    5. 2.5 增量算法
    6. 2.6 总结
  4. 3. 分代思想
  5. 4. 对象的可触及性
  6. 5. Stop-The-World (STW) 现象
  7. 6. 垃圾回收器的种类
    1. 6.1 新生代串行收集器
    2. 6.2 老年代串行收集器
    3. 6.3 并行收集器
    4. 6.4 新生代并行回收收集器
    5. 6.5 老年代并行回收收集器
    6. 6.6 CMS收集器
    7. 6.7 G1收集器
  8. 7. GC 相关的参数