【Jvm】垃圾回收

本篇结构:

  • 判断对象已死
  • java 四种引用
  • 堆内存划分
  • 垃圾收集算法
  • 对象在堆中的分配
  • 垃圾收集器
  • 参考资料

一、判断对象已死

1.1、引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使用的。

这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。

如下面代码所示:除了对象objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为0,于是引用计数算法无法通知 GC 回收器回收他们。

public class ReferenceCountingGc {
    Object instance = null;
    public static void main(String[] args) {
        ReferenceCountingGc objA = new ReferenceCountingGc();
        ReferenceCountingGc objB = new ReferenceCountingGc();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
    }
}

1.2、可达性分析算法

这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。

在Java中,可以作为GC Roots的对象包括下面几种:

  • 虚拟机栈中引用的对象;
  • 方法区中类静态属性引用的对象;
  • 方法区中的常量引用的对象;
  • 本地方法栈中JNI(即一般说的Native方法)的引用的对象;

二、java 四种引用

无论是通过引用计数法判断对象引用数量,还是通过可达性分析法判断对象的引用链是否可达,判定对象的存活都与“引用”有关。

JDK1.2 以后,Java对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)。

2.1、强引用

Java 中默认声明的就是强引用,比如:

Object obj = new Object(); //只要obj还指向Object对象,Object对象就不会被回收
obj = null;  //手动置null

只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,Jvm 也会直接抛出 OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,Jvm 就可以适时的回收对象了。

2.2、软引用(SoftReference)

软引用是用来描述一些非必需但仍有用的对象,在 Java 中用 java.lang.ref.SoftReference 类来表示。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。

// 获取页面进行浏览
Browser prev = new Browser();
// 浏览完毕后置为软引用
SoftReference sr = new SoftReference(prev);
// 还没有被回收器回收,直接获取
if (sr.get() != null) {    
    rev = (Browser) sr.get();
} else {    
    // 由于内存吃紧,所以对软引用的对象回收了    
    // 重新构建    
    prev = new Browser();    
    sr = new SoftReference(prev);
}

2.3、弱引用(WeakReference)

弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。

private static void testWeakReference() {    
    for (int i = 0; i < 10; i++) {
        byte[] buff = new byte[1024 * 1024];
        WeakReference<byte[]> sr = new WeakReference<>(buff);
        list.add(sr);
    }   System.gc(); //主动通知垃圾回收
    for(int i=0; i < list.size(); i++){
        Object obj = ((WeakReference) list.get(i)).get();
        System.out.println(obj);
    }
}

输出 null。

2.4、虚引用(PhantomReference)

虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。

虚引用主要用来跟踪对象被垃圾回收的活动

public class PhantomReference<T> extends Reference<T> {
    /**
     * Returns this reference object's referent.  Because the referent of a
     * phantom reference is always inaccessible, this method always returns
     * <code>null</code>.
     *
     * @return  <code>null</code>
     */
    public T get() {
        return null;
    }
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

2.5、引用队列(ReferenceQueue)

引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去。程序可以通过判断引用队列中是否已经加入了引用,来判断被引用的对象是否将要被垃圾回收,这样就可以在对象被回收之前采取一些必要的措施。

与软引用、弱引用不同,虚引用必须和引用队列一起使用。

三、堆内存划分

堆内存被分成新生代和年老代两个部分。

3.1、新生代

新生代使用复制和标记-清除垃圾收集算法,研究表明,新生代中98%的对象是朝生夕死的短生命周期对象,所以不需要将新生代划分为容量大小相等的两部分内存,而是将新生代分为Eden区,Survivor from 和 Survivor to 三部分,其占新生代内存容量默认比例分别为8:1:1,其中 Survivor from 和 Survivor to 总有一个区域是空白,只有 Eden 和其中一个 Survivor 总共90%的新生代容量用于为新创建的对象分配内存,只有10%的 Survivor 内存浪费,当新生代内存空间不足需要进行垃圾回收时,仍然存活的对象被复制到空白的 Survivor 内存区域中,Eden 和非空白的 Survivor 进行标记-清理回收,两个 Survivor 区域是轮换的。

新生代中98%情况下空白 Survivor 都可以存放垃圾回收时仍然存活的对象,2%的极端情况下,如果空白 Survivor 空间无法存放下仍然存活的对象时,使用内存分配担保机制,直接将新生代依然存活的对象复制到年老代内存中,同时对于创建大对象时,如果新生代中无足够的连续内存时,也直接在年老代中分配内存空间。

Java 虚拟机对新生代的垃圾回收称为Minor GC,次数比较频繁,每次回收时间也比较短。

使用 java 虚拟机 -Xmn 参数可以指定新生代内存大小。

3.2、年老代

年老代中的对象一般都是长生命周期对象,对象的存活率比较高,因此在年老代中使用标记-整理垃圾回收算法。

Java虚拟机对年老代的垃圾回收称为MajorGC/Full GC,次数相对比较少,每次回收的时间也比较长。

当新生代中无足够空间为对象创建分配内存,年老代中内存回收也无法回收到足够的内存空间,并且新生代和年老代空间无法在扩展时,堆就会产生OutOfMemoryError异常。

java虚拟机-Xms参数可以指定最小内存大小,-Xmx参数可以指定最大内存大小,这两个参数分别减去Xmn参数指定的新生代内存大小,可以计算出年老代最小和最大内存容量。

3.3、堆内存示示意图

Hotspot JDK1.8 之前的堆内存示意图:

从上图可以看出堆内存的分为新生代、老年代和永久代。新生代又被进一步分为:Eden 区+Survior1 区+Survior2 区。值得注意的是,在 JDK 1.8 中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。

四、垃圾收集算法

4.1、标记-清除算法

最基础的垃圾收集算法,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象。

标记-清除算法的缺点有两个:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不连续的内存碎片,空间碎片太多会导致当程序需要为较大对象分配内存时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

4.2、复制算法

为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。

复制算法的缺点显而易见,可使用的内存降为原来一半。

4.3、标记-整理算法

标记-整理算法在标记-清除算法基础上做了改进,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。

标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。

复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记-整理算法效率会大大提高。

4.4、分代收集算法

根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。

当前虚拟机的垃圾手机都采用分代收集算法,比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。

五、对象在堆中的分配

5.1、对象优先在 eden 区分配

大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次Minor GC。

新生代GC(Minor GC):指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。

老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC经常会伴随至少一次的Minor GC(并非绝对),Major GC的速度一般会比Minor GC的慢10倍以上。

测试:

/**
 * -XX:+PrintGCDetails
 */
public class GCTest {

    public static void main(String[] args) {
        byte[] allocation1, allocation2;
        allocation1 = new byte[59800 * 1024];
        //allocation2 = new byte[900*1024];
    }
}

添加的 VM 参数:-XX:+PrintGCDetails 打印 GC 信息。

Heap
 PSYoungGen      total 76288K, used 65536K [0x000000076b400000, 0x0000000770900000, 0x00000007c0000000)
  eden space 65536K, 100% used [0x000000076b400000,0x000000076f400000,0x000000076f400000)
  from space 10752K, 0% used [0x000000076fe80000,0x000000076fe80000,0x0000000770900000)
  to   space 10752K, 0% used [0x000000076f400000,0x000000076f400000,0x000000076fe80000)
 ParOldGen       total 175104K, used 0K [0x00000006c1c00000, 0x00000006cc700000, 0x000000076b400000)
  object space 175104K, 0% used [0x00000006c1c00000,0x00000006c1c00000,0x00000006cc700000)
 Metaspace       used 3261K, capacity 4568K, committed 4864K, reserved 1056768K
  class space    used 349K, capacity 392K, committed 512K, reserved 1048576K

可以看出eden区内存几乎已经被分配完全,假如再为 allocation2 分配内存会出现什么情况呢?

简单解释: 因为给 allocation2 分配内存的时候 eden 区内存几乎已经被分配完了,当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。GC 期间虚拟机又发现 allocation1 无法存入Survior空间,所以只好通过 分配担保机制 把新生代的对象提前转移到老年代中去,老年代上的空间足够存放 allocation1,所以不会出现 Full GC。执行 Minor GC 后,后面分配的对象如果能够存在 eden 区的话,还是会在eden区分配内存。

5.2、大对象直接进入老年代

大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。

为什么要这样呢?

为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。

5.3、长期存活的对象将进入老年代

虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别那些对象应放在新生代,那些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。

如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为1。对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

5.4、动态对象年龄判定

为了更好的适应不同程序的内存情况,虚拟机不是永远要求对象年龄必须达到了某个值才能进入老年代,如果 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需达到要求的年龄。

六、垃圾收集器

6.1、Serial 收集器

Serial(串行)垃圾收集器是最基本、发展历史最悠久的收集器;JDK1.3.1前是HotSpot新生代收集的唯一选择。

6.1.1、特点

它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。

新生代采用复制算法,老年代采用标记-整理算法。

6.1.2、应用场景

依然是HotSpot在Client模式下默认的新生代收集器,也有优于其他收集器的地方:

  • 简单高效(与其他收集器的单线程相比);
  • 对于限定单个 CPU 的环境来说,Serial收集器没有线程交互(切换)开销,可以获得最高的单线程收集效率;
  • 在用户的桌面应用场景中,可用内存一般不大(几十M至一两百M),可以在较短时间内完成垃圾收集(几十MS至一百多MS),只要不频繁发生,这是可以接受的。

6.1.3、设置参数

-XX:+UseSerialGC:添加该参数来显式的使用串行垃圾收集器;

6.2、ParNew 收集器

6.2.1、特点

ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(如Serial收集器可用控制参数、收集算法、Stop The World、内存分配规则、回收策略等)和Serial收集器完全一样。

新生代采用复制算法,老年代采用标记-整理算法。

6.2.2、应用场景

在Server模式下,ParNew 收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与 CMS 收集器配合工作;

但在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。

6.2.3、设置参数

  • -XX:+UseConcMarkSweepGC:指定使用 CMS 后,会默认使用 ParNew 作为新生代收集器;
  • -XX:+UseParNewGC:强制指定使用 ParNew;
  • -XX:ParallelGCThreads:指定垃圾收集的线程数量,ParNew 默认开启的收集线程与 CPU 的数量相同;

6.2.4、为什么只有 ParNew 能与 CMS 收集器配合

CMS是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作;

CMS作为老年代收集器,但却无法与JDK1.4已经存在的新生代收集器Parallel Scavenge配合工作;

因为Parallel Scavenge(以及G1)都没有使用传统的GC收集器代码框架,而另外独立实现;而其余几种收集器则共用了部分的框架代码。

6.3、Parallel Scavenge 收集器

6.3.1、特点

Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。

Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,手工优化存在的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。

新生代采用复制算法,老年代采用标记-整理算法。

6.3.2、应用场景

高吞吐量为目标,即减少垃圾收集时间,让用户代码获得更长的运行时间;

当应用程序运行在具有多个CPU上,对暂停时间没有特别高的要求时,即程序主要在后台进行计算,而不需要与用户进行太多交互;

例如,那些执行批量处理、订单处理、工资支付、科学计算的应用程序。

6.3.2、设置参数

Parallel Scavenge 收集器提供两个参数用于精确控制吞吐量:

  • -XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间,大于0的毫秒数,MaxGCPauseMillis 设置得稍小,停顿时间可能会缩短,但也可能会使得吞吐量下降,因为可能导致垃圾收集发生得更频繁;
  • -XX:GCTimeRatio:设置垃圾收集时间占总时间的比率,0<n<100 的整数,GCTimeRatio相当于设置吞吐量大小,垃圾收集执行时间占应用程序执行时间的比例的计算方法是:1 / (1 + n),默认值是1%–1/(1+99),即n=99;
  • -XX:+UseAdptiveSizePolicy:开启这个参数后,就不用手工指定一些细节参数,如:新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、对象超过多大是直接在旧生代分配(-XX:PretenureSizeThreshold)等,JVM会根据当前系统运行情况收集性能监控信息,动态调整这些参数,以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomiscs)。

这是一种值得推荐的方式:

  • 只需设置好内存数据大小(如"-Xmx"设置最大堆);
  • 然后使用 -XX:MaxGCPauseMillis 或 -XX:GCTimeRatio 给 JVM 设置一个优化目标,那些具体细节参数的调节就由JVM自适应完成。

​ 这也是Parallel Scavenge收集器与ParNew收集器一个重要区别。

6.4、Serial Old 收集器

Serial Old是 Serial 收集器的老年代版本;它主要有两大用途:一种用途是在JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。

它主要:

  • 针对老年代;
  • 采用"标记-整理"算法(还有压缩,Mark-Sweep-Compact);
  • 单线程收集。

6.5、Parallel Old 收集器

Parallel Scavenge收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。

JDK1.6 中才开始提供,特点有:

  • 针对老年代;
  • 采用"标记-整理"算法;
  • 多线程收集。

“-XX:+UseParallelOldGC”:指定使用Parallel Old收集器。

6.6、CMS 收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。

CMS(Concurrent Mark Sweep)收集器是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。

CMS收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:

  • 初始标记: 需要"Stop The World",标记一下GC Roots 能直接关联到的对象,速度很快 ;
  • 并发标记: 进行 GC Roots Tracing 的过程,刚才产生的集合中标记出存活对象,应用程序也在运行,并不能保证可以标记出所有的存活对象;
  • 重新标记: 为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录;需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;采用多线程并行执行来提升效率;
  • 并发清除: 开启用户线程,同时 GC 线程开始对为标记的区域做清扫。

6.6.1、特点

  • 针对老年代;
  • 基于"标记-清除"算法(不进行压缩操作,产生内存碎片);
  • 以获取最短回收停顿时间为目标;
  • 并发收集、低停顿;
  • 需要更多的内存。

6.6.2、应用场景

  • 与用户交互较多的场景;
  • 希望系统停顿时间最短,注重服务的响应速度;
  • 以给用户带来较好的体验;
  • 如常见WEB、B/S系统的服务器上的应用。

6.6.3、设置参数

-XX:+UseConcMarkSweepGC:指定使用CMS收集器。

6.6.4、CMS 收集器 3 个明显的缺点

  • 对CPU资源非常敏感:并发收集虽然不会暂停用户线程,但因为占用一部分CPU资源,还是会导致应用程序变慢,总吞吐量降低;CMS的默认收集线程数量是=(CPU数量+3)/4;当CPU数量多于4个,收集线程占用的CPU资源多于25%,对用户程序影响可能较大;不足4个时,影响更大,可能无法接受。
  • 无法处理浮动垃圾,可能出现"Concurrent Mode Failure"失败:在并发清除时,用户线程新产生的垃圾,称为浮动垃圾;这使得并发清除时需要预留一定的内存空间,不能像其他收集器在老年代几乎填满再进行收集;也要可以认为CMS所需要的空间比其他垃圾收集器大;"-XX:CMSInitiatingOccupancyFraction":设置CMS预留内存空间;JDK1.5默认值为68%;JDK1.6变为大约92%;
  • 产生大量内存碎片:由于CMS基于"标记-清除"算法,清除后不进行压缩操作;产生大量不连续的内存碎片会导致分配大内存对象时,无法找到足够的连续内存,从而需要提前触发另一次Full GC动作。

6.7、G1 收集器

G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器。以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。

G1收集器运作过程:

  • 初始标记(Initial Marking):仅标记一下GC Roots能直接关联到的对象;且修改TAMS(Next Top at Mark Start),让下一阶段并发运行时,用户程序能在正确可用的Region中创建新对象;需要"Stop The World",但速度很快;
  • 并发标记(Concurrent Marking):进行GC Roots Tracing的过程;刚才产生的集合中标记出存活对象;耗时较长,但应用程序也在运行;并不能保证可以标记出所有的存活对象;
  • 最终标记(Final Marking):为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录;上一阶段对象的变化记录在线程的Remembered Set Log;需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;采用多线程并行执行来提升效率;
  • 筛选回收(Live Data Counting and Evacuation):首先排序各个Region的回收价值和成本;然后根据用户期望的GC停顿时间来制定回收计划;最后按计划回收一些价值高的Region中垃圾对象;可以并发进行,降低停顿时间,并增加吞吐量。

6.7.1、特点

被视为 JDK1.7中HotSpot 虚拟机的一个重要进化特征。它具备特点:

  • 并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
  • 分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。
  • 结合多种垃圾收集算法,空间整合,不产生碎片:与CMS的“标记–清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
  • 可预测的停顿:这是G1相对于CMS的另一个大优势,降低停顿时间是G1 和 CMS 共同的关注点,但G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内。

6.7.2、应用场景

  • 面向服务端应用,针对具有大内存、多处理器的机器;
  • 最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案;如:在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒。

用来替换掉JDK1.5中的CMS收集器,在下面的情况时,使用G1可能比CMS好:

  • 超过50%的 Java 堆被活动数据占用;
  • 对象分配频率或年代提升频率变化很大;
  • GC 停顿时间过长(长于0.5至1秒)。

6.7.3、设置参数

  • “-XX:+UseG1GC”:指定使用G1收集器;
  • “-XX:InitiatingHeapOccupancyPercent”:当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45;
  • “-XX:MaxGCPauseMillis”:为G1设置暂停时间目标,默认值为200毫秒;
  • “-XX:G1HeapRegionSize”:设置每个Region大小,范围1MB到32MB;目标是在最小Java堆时可以拥有约2048个Region。

七、参考资料:

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页