JVM 中的垃圾回收

时间:2022-10-27 09:00:20

说到JVM,总是绕不开垃圾回收,因为其是JVM的核心之一,因为对象的创建是非常频繁的,想要提高程序的执行效率,拥有一个高效的垃圾回收机制是必不可少的。

首先要明确,因为对象的创建只在堆中,所以垃圾回收主要发生在堆中,但是垃圾回收并不只是回收对象,也会回收一些废弃的常量和类对象。所以垃圾回收作用的位置是在堆和方法区上的。

垃圾的定位和执行

定位

当一个对象没有被引用时就可以被回收,但是问题是如何判断一个对象没有被引用呢?目前确定一个对象是否被引用有两种方法。

1、引用计数法。为对象创建一个计数器,当这个对象被引用时计数器加1,失去引用时减1。在垃圾回收时会判断计数器的值是否为0,如果为0说明可以回收,不为0不可以。

2、可达性分析。从 GC Roots 开始向下搜索,将所有结果的方法的对象全部记录下来,如果当前对象不在这个记录中,就是可以被回收的。(HotSpot 使用)

GC Roots 包括以下元素:

1、虚拟机栈引用的对象。如各个线程被调用的方法中使用的参数、局部变量等。

2、本地方法栈内本地方法引用的对象。

3、方法区中类属性引用的对象。

4、方法区常量引用的对象。如字符串常量池中引用的对象(1.7 开始)

5、同步锁持有的对象。

6、JVM 内部的一些引用(基本数据类型对应的 Class 对象,一些常驻的异常对象)

7、在回收局部对象时,其他位置的对象也可以作为 "临时 GC Roots" 。比如只回收新生代的对象,那么老年代和方法区的对象 可以作为 GC Roots。

比较如果单从效率来看,引用计数法是好于可达性分析的,因为引用计数法只要进行一次判断就可以了。但是 HotSpot 使用的却是第二种,这是因为引用计数法存在回收的 bug。加入有两个对象,其各自的属性值都是对方的对象,那么这两个的引用都不为0,但是他们形成了一个环状引用,并没有被外部所引用,所以是应该被回收的,但是以为引用计数法的规则又不会被回收,这就造成了内存泄漏,为 OOM 埋下了隐患。

回收方法 Finalize

过程:一个对象被回收前,至少会被进行 "两次标记"。当一个对象进行不可达时,会对其进行第一次标记,标记为 "可回收状态"。随后会检查其是否重写了 finalize 方法(Object 类的方法,所以每个对象都有),如果没有重写,那么直接将其标记为 "没有必要执行",然后加入待回收队列,随后等待回收。如果重写了 finalize 方法,那么就会让一个优先级较低的线程去执行这个方法(之所以优先级较低,是防止重写的方法中出现死循环影响程序执行,所以 finalize 方法在回收前并不一定会执行完),在执行完后也会将其加入待回收队列等待回收。

而如果在执行 finalize 方法时,让这个对象再次被 GC Roots 引用,那么这个对象在第二次标记时,就会被移出待回收队列,随后当这个对象再一次进行不可达状态时,这时就会直接进入待回收队列,而不会再次执行 finalize 方法。

注意

1、finalize 方法是在对象回收前执行的方法,所以可以在这个方法中进行一些资源的释放、清理工作。所以永远不要去主动调用一个对象的 finalize 方法。

2、finalize 方法重写需慎重,不然会影响程序的执行效率。

3、如果想要主动回收某个类,可以使用 System.gc() 通知 GC 执行 full gc,但是只是通知,GC 会根据当前情况来判断是否会执行。

安全点和安全区域

安全点

程序执行时并非所有地方都可以停顿下来进行 GC,只有在特定的位置才能停顿下来开始 GC,这些位置称为 "安全点"。安全点一般选取一些执行时间长的操作,保证运行时的性能受到的影响很小。

在GC 发生时,如何保证所有的线程都跑到最近的安全点停下来呢?

1、抢先式中断(未使用)。首先中断所有线程,然后某个线程不在安全点上,就再启动让其执行到安全点再中断。

2、主动式中断。设置一个中断标志,当线程执行到安全点就开始轮询这个标志,然后判断中断状态是否为真,如果为真就将自己中断挂起。

安全区域

当线程处于 "未执行" 状态时发生 GC ,那么因为这个线程没有处于安全点,所以 GC 就会等这个线程执行到安全点才会进行 GC ,这样是十分耗时的,所以引入了安全区域的概念。安全区域就是一段引用关系不会发生变化的区域。比如调用 sleep()、wait() 方法。

实际执行

首先会排除处于安全区域的线程,然后判断剩下的线程是否都处于安全点,在发出 GC 通知后,会将中断状态设为 true,而执行到安全点的线程就会轮询判断、挂起。最后当检测所有的线程都进入安全点后就会执行 GC。

垃圾回收过程

堆的结构

首先要知道,堆在不同时期的结构是不一样的,在 G1 垃圾回收器之前,堆是下图的结构。由于 G1 垃圾回收器比较复杂,同时回收过程会用到之前的基础,所以先以 G1 之前为例来看。

JVM 中的垃圾回收

堆主要分为新生代和老年代,新生代,也就是图中的 Young 区和 Old 区。而 Permanent 对应的是永久代,虽然其是方法区的实现,但是从逻辑上来看也属于堆。

在新生代又分为 Eden 区和两个 Survivor 区。比例默认为 8:1:1,可以通过 -XX:SurvivorRatio 来修改这个比例。新生代用于存放新创建并且占空间较小的对象。老年代用于存放存活达到一定时间或占空间较大的对象。

回收类型

1、部分回收:

  1)Minor GC(Young GC):发生在新生代的 GC,当 Eden 区满后就会触发 Minor GC。只收集新生代的垃圾。

  2)Major GC:发生在老年代的 GC,只收集老年代的垃圾。目前只有 CMS 支持单独回收老年代的行为。

  3)Mix GC:混合回收,收集新生代和老年代的垃圾。

2、整堆收集(Full GC)。收集整个 java 堆和方法区的垃圾。方法区收集的对象是未引用的常量以及类。之前的文章也说过,类被回收的条件非常苛刻,必须满足下面三个条件:

  1)该类对应的对象全部被回收

  2)该类对应的 Class对象无法被访问‘

  3)加载该类的类加载器被回收

虽然分为这么多种类,但是一般主要执行的是 Minor GC 和 Full GC,Major GC 一般情况下都是伴随着 Full GC 的执行,所以我们一般只考虑这两个 GC。Full GC 消耗的时间是 Minor GC 的十倍以上,并且 GC 会造成 STW(stop the world,也就是工作线程全部暂停)所以应该避免 Full GC。

执行过程

JVM 中的垃圾回收

在堆刚初始化时,新建的第一个对象会存入新生代的 Eden 区,如果对象过大那么会进行一次 Minor GC,随后放入新生代两个 Survivor 区的其中一个中,然后还是不够大,那么直接放入老年代,如果老年代空间再不够存放那么直接执行 full gc,所以并不是所有的对象都会在新生代分配对象。当对象存入后,随着对象的越来越多,当 Eden 区满了后,就会触发 Minor GC,将Eden 区以及其中一个存放 Survivor 区的对象进行标记、回收,然后将存活的对象全部存入另一个空的 Survivor 区中。而每次执行一次 Minor GC,存活下来的对象都会将其对象头的GC年龄部分+1,当达到一定年龄后,这个对象就会随着下此GC晋升到老年代(CMS默认为6,其他默认都是15)。

空间分配担保

由于新生代的晋升机制,使得每一次 Monir GC 晋升到老年代的对象都可能会导致老年代空间不足从而发生 Full GC,所以 JVM 维护了一个空间分配担保机制。避免发生 Minor GC 之后又发生 Full GC,最大程度地影响了程序的执行效率。

在发生 Minor GC 之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。

1、大于。说明此次 Minor GC 是安全的,直接执行 Minor GC。

2、小于。查看设置的 HandlePromotionFailure,这个值表示是否允许担保失败,如果是 true,那么会继续检查老年代最大可用的连续内存是否大于历代晋升到老年代的对象平均大小,

                                          如果大于,则尝试执行 Minor GC,此次GC 是存在风险的,因为可能晋升的对象会造成 Full GC;

                                     如果小于或者这个参数值为 false,那么直接执行 Full GC。

上面的规则是 JDK6 之前的,在 JDK6 开始, HandlePromotionFailure 不会再作为影响执行的因素了。而是直接检查老年代可用的最大连续内存是否大于历代晋升的对象平均值,如果大于执行 Minor GC,否则执行 Full GC。

注意

1、并不是所有的对象都需要等到 GC 达到指定的年龄后才能晋升。当某个年龄的对象占空间达到 survivor 区的一半时,那么 survivor 区中所有大于这个年龄的对象都在在下一次 GC 时晋升到老年代。

2、并不是 OOM 都会触发垃圾回收器。对于超大对象,大小直接超过堆的总大小,JVM 会判断这个对象是垃圾回收无法解决的,直接抛出 OOM。

触发时机

Minor GC:

1、Eden 区空间无法存放新对象的。

2、Full GC 会触发 Minor GC。

Full GC:

1、空间分配担保,老年代可用的连续内存小于历代晋升的对象平均值。

2、分配给老年代的对象大于老年代可用的最大连续内存

垃圾回收算法

基础算法:

1、标记-清除:对不需要清理的对象标记、清除,不作任何额外操作。

缺点:会产生内存碎片,使得 GC与 OOM 更容易发生。

优点:1、执行快。2、内存利用率高

注意:这里的清理并不是直接删除对象,而是将对象的地址保存到空闲列表中,在 对象的创建和分配 中说过对于对象保存不规整的堆,分配空间是需要维护一个空闲列表来记录可用的位置,而标记-清除就是会造成对象内存不规整的场景,所以分配空间就是通过空闲列表,而一个对象被标为可回收后就可以直接加入空闲列表,然后下一次分配时就可以直接覆盖原有的对象。

2、复制算法:复制算法一般是使用两个相同大小的区域,两块区域中永远有一个永远保持清空状态,当垃圾回收时,将存活下来的对象移入空的区域中,然后清空剩下的所有对象,等待下一次GC时再反过来。前面说到的新生代的 Minor GC 采用的就是复制算法。

优先:1、效率高。2、实现简单。

缺点:1、内存利用率不足。2、在对象存活率高的场景中使用会影响效率。(这也是为什么老年代使用的不是复制算法)

3、标记-整理算法:将不需要的对象标记,然后移到一起,再删除剩下的对象。HotSpot 老年代使用的算法。

优点:1、没有产生内存碎片,避免了更频繁的 GC与 OOM。2、内存利用率高

缺点:1、每次 GC 都需要去改变对象地址,效率较低。

组合算法

组合算法其底层还是使用前面三种基础算法,只不过在此基础上做了一些组合拓展。

1、分代算法:根据对象的存活周期将对象划分为不同的部分,每个部分使用每个部分的回收算法。这也是 HotSpot 使用的算法。

2、增量收集算法:将GC过程分为多段,每次只执行一部分。优点是用户体验会更好。缺点是切换会产生上下文切换的成本,造成系统的吞吐量( 用户线程时间 / (用户线程时间+GC线程STW时间) )降低。

3、分区算法:将堆拆分成多个小分区,每次回收只需要对每块小分区内的对象进行操作。提升了执行效率。

垃圾回收器

垃圾回收器是垃圾回收过程的核心之一,随着 JDK 版本的迭代,垃圾回收器也经历了多次迭代变化,新出的垃圾回收器越来越强大,但是不能说新出的垃圾回收器就一定强于之前的回收器。其执行效率的高低回收要看使用场景。

分类

按线程数:

串行垃圾回收器:单线程执行GC

并行垃圾回收器:多线程执行GC

按工作模式:

并发式垃圾回收器:和用户线程交替执行,实现的有 CMS、G1

独占式垃圾回收器:在GC时会停止所有的用户线程,也就是STW

按碎片处理:

压缩式垃圾回收器:会进行内存整理,不会产生内存碎片。

非压缩式垃圾回收器:不会进行内存整理,使用空闲列表来完成对象的空间分配。会产生内存碎片。

按工作的内存区间:

年轻代垃圾回收器:回收新生代。

老年代垃圾回收器:回收老年代。

吞吐量与低延时关系

吞吐量是衡量用户线程的执行效率的,其计算公式是 " 用户线程执行时间 / (用户线程执行时间+GC造成的 STW时间) "。吞吐量越高的程序执行效率越高。

低延时是指用户一次GC时造成的 STW 时间比较短,其主要是用于提高用户的体验,上面也说过 "增量收集算法",就是将 GC 分为多次,使得单次的 GC STW 时间较短,使得用户体验更好,但是切换引起的上下文切换成本会使 吞吐量降低。

所以,吞吐量和低延时是鱼和熊掌,不可兼得,要想吞吐量高就必须牺牲低延时;而如果想要低延时,就必须牺牲一定的吞吐量。

实现

1、Serial 收集器。

新生代垃圾回收器,单线程串行。在单核CPU下执行效率高。

2、ParNew 收集器。

新生代,并行垃圾回收器。是 Serial 的多线程版本。在 JDK后面版本被孤立了(没有与它搭配的老年代回收器了)

3、Parallel Scavenge 收集器。

新生代,并行收集器。与 ParNew 的区别是可以已有更高的吞吐量,高效利用 CPU。

4、Serial Old 收集器。

老年代,单线程串行。是 Serial 的老年代版本。

5、Parallel Old 收集器。

老年代,并行收集器。追求高吞吐量。是Parallel Scavenge 的老年代版本。

小结:上面五种收集器实现都比较简单,都是独占式垃圾回收器,并行垃圾回收器,新生代的三个使用的都是复制算法,老年代的两个都是使用标记-整理算法。在执行时都会造成 STW。而下面的三种垃圾回收器则可以与用户线程并发执行,兼顾了低延时和吞吐量。

CMS 垃圾回收器

CMS 是老年代的垃圾回收器,使用的是标记-清除算法。他不是和前几个一样直到对象满后才会进行GC,而是达到某个阀值就会开始垃圾回收。 

过程:

1、初始标记。标记主方法直接关联的对象,此过程会造成 STW,但消耗的时间较短。

2、并发标记。标记所有不可达对象,这一步是与用户线程并发执行的,不会造成 STW。 但是会造成错标漏标,比如刚刚扫描完的对象在用户线程的作用下变成不可达状态,或者某个对象在扫描标记为可回收后又被用户线程引用。所以在第二步之后还需要进行进一步的检查。

3、重新标记。修改刚才错标的情况。但是对于漏标的(扫描时是可达,随后变成不可达)对象不会修正。这一步也会造成 STW。但是以为错标的对象在所有需要回收的对象中占比较小,所以执行的效率还是较高的,只比第一步初始标记消耗的时间略长。

4、并发清除。并发清除所有的不可达对象,不会发生 STW。

CMS 在与用户线程并发时可能会造成某些对象漏标导致对象空间不足,这时因为 CMS 还在工作,无法再回收,所以,当 CMS 在并发时出现内存空间不足时,首先会抛出 "Concurrent Mode Failure" 异常,然后启用备用的垃圾回收器:Serial Old 进行回收。

JVM 中的垃圾回收

为什么 CMS 采用的是标记-清理算法

CMS 与用户线程是并发执行的,如果采用标记-整理算法,那么就可能会在与用户并发执行时整理对象,这时对象的地址就会改变,影响用户线程的执行。导致线程崩溃。

小结:

优点:CMS 是 JDK 首款并发的并发式垃圾回收器。它的出现使得垃圾回收过程与用户线程可以同时执行,在提高程序吞吐量的同时也降低了延时。

缺点:1、因为降低了延时,所以吞吐量是会有所降低的。2、因为是标记=清除算法,所以会导致 full gc 与 OOM 发生的概率更高。

G1 垃圾回收器

G1 是在 CMS 的思路改造的。但其也是具有划时代的意义的。其打破了传统的模式,引入了分区的概念。将整个的堆划分为多个 region 区域。每块 region 区种类可以是 eden、survivor、old、Humongous(专门存储大对象,一般的大小是一般 region 的。15倍,大对象优先选择 H 区存储,当一个H区存储不下就会区寻找连续的H区存放)。新生代对象还是优先存入 eden 和 survivor 区中,到达年龄后晋升到老年代的 region 区,实现逻辑分代,物理不分代。

特点

1、由于将堆划分为多个 region区,所以在回收时只需要将其中存活的对象移入相邻空闲的 region区,再回收就可以了,效率大大提高,也不会产生内存碎片,同时也不会使利用率降低。微观来看不管是新生代回收老年代都是复制算法。宏观来看是标记-整理算法。

2、可预测的停顿时间模型。让使用者明确指定一个长度为 M 毫秒的时间内,消耗在垃圾收集的时间不得超过 N 毫秒。G1 会根据各个 region 区回收时间维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 region。比如指定时间是 20ms,那么后台就会从优先列表里选择总耗时低于 20ms 的优先级高的若干块 region 区进行回收。可以通过  -XX:MaxGCPauseMills 设置,G1 会尽力实现,但不能保证一定可以实现。

3、Remebered Set机制。在每个 region 区都维护一个 Remebered Set(RSet)表,存储当前 region 中对象被其他 region 区对象的引用详情。这样在GC时就可以将所有的 RSet 表数据加入 GC  Roots根节点的枚举范围中,避免了全局扫描,同时也不会遗漏。而在记录时会先将记录记载到中间表 dirty card queue 中,等到 GC 时。这样是因为 Rest 表更新是需要线程同步的,为了避免频繁的同步影响程序性能,所以使用 dirty card queue 作为中间表。

过程

1、年轻代GC(Minor GC)。当所有的 eden 类型的 region 区总容量满了后,对 eden 和 survivor 区进行回收,晋升的放入老年代,其余存活的寻找有空闲位置的 survivor 区存储。在标识时会使用 RSet 和 dirty card queue 来记录协助GC。

2、并发标记(老年代标记)。当老年代内存使用占比达到一定值(默认45%)后,开始进行老年代并发标记过程。这一过程主要是对各个区域进行扫描,计算要回收的对象活性(GC回收对象的比例)并排序,清理完全是垃圾的 region 区。

  1)首先执行初始标记(STW)。标记从根节点直接可达的对象。

  2)跟区域扫描。扫描 Survivor 区直接可达的老年代区域对象,这一步必须在 Young GC 之前完成。

  3)并发标记。在用户线程并发执行,标记整个堆中的存活对象,如果某个 region 区全部都是垃圾,那么就会立刻回收该区域。然后计算每块区域的对象活性(存活对象比例)

  4)再次标记。因为上一步是并发的,所以这一步是解决漏标的对象,但是使用了比 CMS 更快的初始快照算法。

  5)独占清理(STW)。计算各个区域的存活对象和 GC回收的比例,并进行排序。

  6)并发清理。识别并清理完全空闲的区域。

3、混合回收。在老年代对象占用区域达到一定比例,会触发混合回收,但不是 full gc。混合回收会回收年轻代和一部分老年代,默认会将老年代回收分八次回收,回收的顺序就是按上一步生成的排序从高到低来进行。但是并一定就是八次,当某个区域垃圾占比小于

-XX:G1MixedGCLiveThresholdPercent(默认65%),那么就不会参与回收。同时还有一个参数 -XX:G1HeapWastePercent(默认10%)设置可浪费的比例,也就是如果要回收的总对象占比小于堆总大小的比例小于这个值,那么就不会进行回收。

优势

功能强大,兼顾吞吐量和低延时,没有内存碎片产生,可以自定义停顿时间。

劣势

需要更高的配置才能启用,在内存小的场景执行效率并没有那么高。

ZGC

是 G1 的升级版,在 G1 分区的基础上,不设分代,使用读屏障、染色指针和内存多重映射等技术来实现可并发的标记-压缩算法的。在吞吐量影响不大的前提下,把垃圾收集的停顿时间限制在十毫秒以内的低延迟。工作的四个阶段:并发标记-并发预备重分配-并发重分配-并发重映射 等。除了初始标记是STW其他都是并行的。因为其暂时还不稳定,所以还不是主流的垃圾回收器。

G1 与 CMS 的对比

1、G1 不会产生垃圾碎片,而CMS 因为是标记-清除算法,会产生碎片,提高 full gc 与OOM 的概率。

2、在条件足够的场景下,G1 的性能要强于 CMS,而 CMS 在有限内存下效率是高于 G1 的。

总结

对于上面提到的这些垃圾回收器,不能说 G1 ,CMS 的执行效率就一定比 ParNew、甚至 Serial 要高,收集器的使用要结合场景,如果是单核的场景,那么 Serial + Serial Old 的效率要高于其他任意一个组合,而如果是多核但内存不够,那么 CMS的效率又会比 G1 要高,只有当条件允许时,G1 的效率才是最好的。

不同版本下垃圾回收器的搭配

JVM 中的垃圾回收

JVM 中的垃圾回收

相关参数

常用

-Xss512k设置单个栈容量512k,一般是512k-1024k;

-Xms2g:初始化推大小为 2g,默认是物理内存的 1/64;

-Xmx2g:堆最大内存为 2g,默认为物理内存的 1/4。

-Xmn125k:新生代大小125k

-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;默认是2。

-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:1:1;默认是8

–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;

-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;

-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;

-XX:+PrintGC:开启打印 gc 信息(打印的内容比较简单);

-XX:+PrintGCDetails:打印 gc 详细信息。

-XX:+PrintGCTimeStamps:输出GC的时间戳(以基准时间的形式)

-XX:+PrintGCDateStamps:输出GC的时间戳(以日期的形式)

-XX:+PrintHeapAtGC:在进行GC的前后打印出堆的信息

-Xloggc:../logs/gc.log:日志文件的输出路径

-XX:+PrintFlagsInitial:查看所有参数的默认初始值

-XX:+PrintFlagsFinal:查看所有参数的最终值(可能存在修改,不再是初始值)

-XX:MaxTenuringThreshold:设置新生代晋升年龄阀值

-XX:+PrintGCDetails:输出详细的GC处理日志

-XX:+DoEscapeAnalysis显式开启逃逸分析

-XX:+PrintEscapeAnalysis查看逃逸分析的筛选结果

-XX:EliminateAllocations开启了标量替换(默认打开),允许将对象打散分配在栈上

-XX:PermSize= :设置永久代初始容量

-XX:MaxPermSize= 设置永久代最大容量

-XX:MetaspaceSize= 设置元空间初始大小(默认21m)

-XX:MaxMetaspaceSize= 设置元空间最大空间

-XX:MaxDirectMemorySize=设置直接内存的大小,默认和堆最大值,也就是-Xmx大小一致。

编译器

-XX:-UseCounterDecay :方法调用计数器关闭热度衰减

-XX:CounterHalfLifeTime :设置半衰周期的时间

-Xint完全使用解释器模式去执行程序

-Xcomp完全采用即时编译器去执行程序。如果程序出现问题,解释器会介入执行。

-Xmixed采用解释器+即时编译器的混合模式共同执行程序。

 字符串常量池

-XX:StringTableSize= :设置字符串常量池的 StringTable 数组长度

-XX:+PrintStringTableStatistics : 开启打印StringTable统计信息

 OOM

-XX:+HeapDumpOnOutOfMemoryError在OOM时自动生成Heapdump文件

垃圾回收器

-XX:+PrintComandLineFlags查看命令行相关参数(包含使用的垃圾收集器)

使用命令行:jinfo -flag 相关垃圾回收器参数 进程ID

-XX:+UseSerialGC:表明新生代使用Serial GC ,同时老年代使用Serial Old GC

ParNew

-XX:+UseParNewGC:标明新生代使用ParNew GC

-XX:ParallelGCThreads设置线程数量,默认开启和CPU数据相同的线程数(在并发量要求小的项目中线程数多会增加线程切换的成本;在并发量要求大的项目中线程数少会导致效率不高)

Parallel Scavenge

-XX:+UseParallelGC:表明新生代使用Parallel GC

-XX:+UseParallelOldGC : 表明老年代使用 Parallel Old GC

 *  说明:二者可以相互激活

-XX:ParallelGCThreads设置线程数量。默认CPU数量小于8时等于8;CPU数量大于8等于3+[5 * CPU_Count]/8

-XX:MaxGCPauseMillis设置垃圾回收器最大停顿时间(也就是延迟时间,单次STW时间),该参数使用需谨慎

-XXGCTimeRatio垃圾收集时间占总时间的比例,用于衡量吞吐量的大小,与前一个参数相矛盾,停顿时间越短,垃圾收集的时间比例就越高。比例公式是1/(n+1),默认是99,也就是垃圾收集时间占比就是1/100。

-XX+UseAdaptiveSizePolicy开启Parallel Scavenge的自适应调节策略。可以通过指定最大堆空间,吞吐量,最大停顿时间(也就是上面两个参数)来让虚拟机自己完成调优工作。

CMS:

-XX:+UseConcMarkSweepGC老年代使用CMS垃圾回收器,开启后会自动打开-XX:+UseParNewGC打开。即:ParNew(新生代)+CMS(老年代)+Serial Old(备用)

-XX:CMSlnitiatingOccupanyFraction设置堆内存使用率的阀值,一旦达到该阀值,便开始进行回收。jdk6之前默认68,6之后默认92。内存增长缓慢可以设置高一些,增长快可以设置低一些。

-XX:+UseCMSCompactAtFullCollection:开启在执行完full gc后对内存空间进行整理,避免内存碎片产生。

-XX:CMSFullGCsBeforeCompaction:搭配上面的参数,设置在执行完多少次full gc后进行碎片整理。

-XX:ParallelCMSThreads:设置CMS的线程数量。默认是(ParallelGCThreads+3)/4

G1:

-XX:+UseG1GC:使用G1收集器

-XX:G1HeapRegionSize:设置每个Region的大小。值是2的幂,范围是1MB到32MB之间。默认是堆内存的1/2000。

-XX:MaxGCPauseMills:设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到)。默认值是200ms。

-XX:ParallelGCThread:设置STW时GC线程数的值,默认是8。

-XX:ConcGCThreads:设置并发标记的线程数。一般设置为上面STW GC线程数的1/4左右。

-XX:InitiatingHeapOccupancyPercent:设置触发并发GC周期的Java堆占用阀值。超过此值,就触发GC,默认值是45。