乐趣区

关于jvm:JVM笔记二-垃圾回收

1. 如何判断对象能够垃圾回收

1.1 援用计数法

统计对象被援用的个数,当援用个数为零,即没有中央在应用该对象时,则能够进行垃圾回收。

弊病:当两个对象循环援用时,援用计数都是 1,导致两个对象都无奈开释。比方对象 A 中援用了对象 B,对象 B 援用计数加 1,同时对象 B 中又援用了对象 A,对象 A 援用计数也加 1,导致这两个对象永远不会被回收。

1.2 可达性分析法

JVM 中的垃圾回收器通过 可达性剖析 来摸索所有存活的对象,即扫描堆中的对象,看是否沿着 GC Root 对象为终点 的援用链找到该对象,如果 找不到,则示意能够回收

能够作为 GC Root 的对象

  • 虚拟机栈(栈帧中的本地变量表)中援用的对象。
  • 办法区中类动态属性援用的对象
  • 办法区中常量援用的对象
  • 本地办法栈中 JNI(即个别说的 Native 办法)援用的对象

1.2.2 五种援用类型

对于不同的援用类型,垃圾回收时有着不同的解决。java 中援用类型个别分为 强援用 软援用 弱援用 虚援用 终结器援用。上面别离介绍这五种援用类型。

— 援用队列

作用:用于回收援用本身

当 软援用、弱援用、虚援用、终结器援用所关联的对象被回收时,援用本身能够进入援用队列,而后 ReferenceHandler 线程会对援用队列中的援用进行对立回收解决。

— 强援用

强援用就是常常应用的援用类型,只有所有 GC Roots 对象都不通过【强援用】援用该对象,该对象能力被垃圾回收。个别通过赋值 null 使强援用的对象被回收。如下:

//obj 为强援用
Object obj = new Object();
//obj 赋值 null 后,通过 obj(GC Root 对象)援用就找不到方才创立的对象了,随后就会被垃圾回收
obj = null;

— 软援用 (SoftReference)

软援用对应的 java 类是 java.lang.ref.SoftReference<T>当对象仅有软援用可达到,在垃圾回收后,内存仍有余时会再次登程垃圾回收,回收该对象 ,能够配合 援用队列 来开释软援用本身。比方上图中的 A2 对象,如果断开 B 对象的强援用,只有软援用指向 A2,垃圾回收时如果内存不足,A2 对象也会被回收,而后软援用进入援用队列。

Object obj = new Object();
//softRef 为软援用
// 另一个构造方法 SoftReference(T referent, ReferenceQueue<? super T> q),可传入援用队列来回收援用本身所占内存
SoftReference<Object> softRef= new SoftReference<>(obj);
// 如果内存短缺,get()返回 obj 对象;如果内存不足,GC 时会回收 softRef 所关联的 obj 对象,此时 get()返回 null
softRef.get();

软援用的在理论利用中,个别是为了防止内存溢出的产生。如果有一批图片资源,须要从磁盘读取并缓存到内存中不便下次读取,而这部分缓存并不是必须的,你不心愿这部分缓存太大导致而内存溢出,那么你就能够思考应用软援用。如下代码,堆内存只有 20m,须要读取 5 张 4m 的图片存入一个 list 中,别离演示应用强援用与软援用:

import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

/**
 * 演示软援用
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 */
public class GcDemo01 {
    private static final int _4MB = 4 * 1024 * 1024;

    public static void main(String[] args) throws IOException {//strong();
        
       soft();}

    // 应用强援用导致堆内存溢出 java.lang.OutOfMemoryError: Java heap space
    public static void strong(){List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {list.add(new byte[_4MB]);
        }
    }

    // 应用软援用,当内存不足垃圾回收时 list 中个别软援用对象所援用的 byte[]对象会被回收,所以 ref.get()可能返回 null
    public static void soft() {// list --> SoftReference --> byte[]
        List<SoftReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {SoftReference<byte[]> softRef = new SoftReference<>(new byte[_4MB]);
            System.out.println(softRef.get());
            list.add(softRef);
            System.out.println(list.size());
        }
        System.out.println("循环完结:" + list.size());
        for (SoftReference<byte[]> ref : list) {System.out.println(ref.get());
        }
    }
}

上诉代码应用软援用形式执行,打印日志如下:

如果在垃圾回收时发现内存不足,在回收软援用所指向的对象时,软援用本身不会被回收 ,如果想要 回收软援用 本身,须要配合应用 援用队列。演示代码如下:

package indi.taicw.jvm.gc;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

/**
 * 演示软援用, 配合援用队列
 */
public class GCDemo02 {
    private static final int _4MB = 4 * 1024 * 1024;

    public static void main(String[] args) {List<SoftReference<byte[]>> list = new ArrayList<>();

        // 援用队列
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

        for (int i = 0; i < 5; i++) {// 关联了援用队列,当软援用所关联的 byte[]被回收时,软援用本人会退出到 queue 中去
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());
        }

        // 从队列中获取无用的 软援用对象,并移除
        Reference<? extends byte[]> poll = queue.poll();
        while(poll != null) {list.remove(poll);
            poll = queue.poll();}

        System.out.println("===========================");
        for (SoftReference<byte[]> reference : list) {System.out.println(reference.get());
        }

    }
}

— 弱援用

弱援用对应的 java 类是 java.lang.ref.WeakReference<T>,弱援用与软援用基本相同,区别是 仅有弱援用可达该对象时,在垃圾回收时,无论内存是否短缺,都会回收弱援用指向的对象

— 虚援用

虚援用对应的 java 类是 java.lang.ref.PhantomReference<T>,与 软援用和弱援用 不同的是,虚援用必须配合援用队列一起应用 ,因而只有一个构造方法 “PhantomReference(T referent, ReferenceQueue<? super T> q)”, 且虚援用的 get()办法始终返回 null

虚援用实际上并不是用来指向 java 对象的,所以 get()办法始终返回 null,虚援用次要作用体现在 开释间接内存 。咱们在应用ByteBuffer.allocateDirect() 申请调配间接内存时,会创立一个虚援用 (Cleaner) 并且指向 ByteBuffer 对象,当 ByteBuffer 对象被回收时,虚援用 (Cleaner) 会进入援用队列中,而后调用 Cleaner#clean()办法来开释间接内存。

— 终结器援用

终结器援用对应的 java 类是 java.lang.ref.FinalReference<T>, 也 必须配合援用队列一起应用 ,终结器援用次要作用是 当对象被真正回收之前,用来调用对象的 finalize()办法的 (所有的类都继承 Object 类,Object 类有一个 finalize() 办法)。

当一个对象重写了 finalize()办法,在垃圾回收时,终结器援用进入援用队列(被援用对象临时还没有被回收),再由 Finalizer 线程通过终结器援用找到被援用对象并调用它的 finalize()办法,第二次 GC 时能力回收被援用对象。

Finalizer 线程优先级比拟低,因而 finalize()办法被执行的几率比拟低,可能会导致资源始终无奈开释,所以个别不倡议重写 finalize()办法来开释资源

2. 垃圾回收算法

2.1 标记革除

分为两个阶段,标记 :首先标记出要回收的对象; 清理:垃圾回收器依据标记革除相应的内存空间(革除并不是把内存空间清理 0,而是记录下这段内存的起始地址为未应用空间,下次内存调配时间接笼罩这块内存就行了)。

特点:容易产生大量内存碎片。当给大对象分配内存时可能会找不到适合的内存空间,就会导致 GC,一旦 GC 程序就会暂定导致利用的响应速度变慢

2.2 标记整顿

分为两个阶段:标记 :首先标记出要回收的对象; 整顿:挪动存活的对象到间断的内存空间。

特点:不会造成内存碎片,然而挪动须要耗费工夫,导致效率低

2.3 复制

把内存空间分为两块大小相等的区域 (FROM/TO),每次只应用其中的一块,当这一块内存空间应用完了,就把 还存活的对象复制到另一块,而后把这块已应用的内存空间一次清理掉。

特点:不会造成内存碎片,是对整个半块内存进行回收不必思考简单状况,实现简略、运行高效,然而须要节约一半的内存空间

3. 分代垃圾回收

3.1 分代区域划分

个别状况垃圾回收不会只单单应用 标记革除、标记整顿、复制 算法中的一种,而是依据状况组合应用这几种算法。

分代垃圾回收算法,依据对象存活周期的不同把内存区域分为两大块 新生代 老年代 ,其中新生代又细分为三个区域: 伊甸园区 (Eden)、 幸存区 FROM(Survive-from)、幸存区 TO(Survive-to)。

新生代 对象的特点个别是朝生夕死,每次垃圾回收都会有少量对象死去,只有大量对象存活,所以适宜采纳 复制算法 进行垃圾回收;

老年代 对象的特点个别存活率高,没有额定的空间进行调配担保,所以就必须采纳 标记清理 或者 标记整顿 算法进行垃圾回收。

3.2 分代垃圾回收过程

分代垃圾回收的过程如下

  1. 对象创立时首先会被调配在 伊甸园区
  2. 伊甸园区 的内存不足时,会进行一次新生代的垃圾回收,这时的回收叫做 Minor GC。Minor GC 的过程就是把 伊甸园区 幸存区 FROM的存活对象复制到 幸存区 TO,且 存活对象寿命加 1 ,而后回收 伊甸园区 幸存区 FROM内存空间

    • Minor GC 会引发STW 事件 (stop the world),即暂停其它所有用户线程,等垃圾回收完结,用户线程才复原运行
  3. 替换 幸存区 FROM 幸存区 TO,即把原来的幸存区 FROM 标记为幸存区 TO,把原来的幸存区 TO 标记为幸存区 FROM

    • 两个幸存区,每次垃圾回收后应该保障其中一个是空的,期待下次垃圾回收时把存活对象复制到该幸存区。个别标记 TO 区为下次复制存活对象的幸存区
  4. 一个对象每经验一次 Minor GC 寿命都会加 1,当 对象寿命超过阈值会降职至老年代 。当老年代空间有余时,就会 触发 FULL GC

    • 可通过参数-XX:MaxTenuringThreshold=threshold 来调整对象从新生代降职到老年代的阈值(最大只能设置到 15)
    • FULL GC 的 STW 工夫会更久,个别是 Minor GC 的 10 倍

对象从 新生代进入老年代的机会,并不是只有寿命达到阈值能力进入老年代,有些状况下即便寿命没有达到也能间接进入老年代:

  • Minor GC 时如果 幸存区 TO 包容不了伊甸园区和幸存区 FROM 存活的对象,那么就会应用老年代空间进行分担,这些对象就会间接进入老年代
  • 当给一个 较大对象分配内存 时,而新生代即便 Minor GC 后残余空间还是无奈包容该对象,那么也会间接进入老年代进行存储

4. 垃圾回收器

4.1 相干概念

串行 (serial) 收集:指只有一个垃圾收集线程进行工作,但此时用户线程处于期待状态

并行 (parallel) 收集:指有多条垃圾收集线程并行工作,但此时用户线程处于期待状态

并发 (concurrent) 收集:用户线程与垃圾收集线程同时执行(不肯定是并行的也可能会交替执行),用户线程仍在继续执行,而垃圾收集线程运行在另一个 CPU 上

吞吐量 单位工夫内CPU 用于运行用户代码的工夫与 CPU 总耗费工夫的比值(吞吐量 = 运行用户代码工夫 / (运行用户代码工夫 + 垃圾收集工夫))。例如:虚拟机共运行 100 分钟,垃圾收集器花掉 1 分钟,那么吞吐量就是 99%

低延时 :尽可能让 单次垃圾回收STW 的工夫最短

个别 并行收集偏差于吞吐量优先,并发收集偏差于低延时优先 ,基于这些个性以及垃圾回收算法常见的垃圾回收器有 7 种,别离 用于新生代和老年代,如下图,两头有直线相连的示意它们能够搭配应用

4.2 Serial 收集器

Serial 收集器 是最根本的、倒退历史最悠久的收集器,用于 新生代垃圾回收 ,采纳 复制算法

特点:单线程、简略高效(与其余收集器的单线程相比),对于限定单个 CPU 的环境来说,Serial 收集器因为没有线程交互的开销,分心做垃圾收集天然能够取得最高的单线程手机效率。收集器进行垃圾回收时,必须暂停其余所有的工作线程,直到它完结(Stop The World)。

利用场景:实用于 Client 模式下的虚拟机。

4.3 Serial Old 收集器

Serial Old 收集器 是 serial 收集器的老年代版本,用于 老年代垃圾回收 ,采纳 标记整顿算法

特点:与 serial 收集器基本一致

利用场景:次要也是应用在 Client 模式下的虚拟机中。也可在 Server 模式下应用。

4.4 ParNew 收集器

ParNew 收集器 其实就是 Serial 收集器的多线程版本,除了应用多线程外其余行为均和 Serial 收集器截然不同(参数管制、收集算法、Stop The World、对象调配规定、回收策略等)。

特点:多线程、ParNew 收集器默认开启的收集线程数与 CPU 的数量雷同,在 CPU 十分多的环境中,能够应用 -XX:ParallelGCThreads 参数来限度垃圾收集的线程数

利用场景:ParNew 收集器是许多运行在 Server 模式下的虚拟机中首选的新生代收集器,因为它是除了 Serial 收集器外,惟一一个能与 CMS 收集器配合工作的

4.5 Parallel Scavenge 收集器

Parallel Scavenge 收集器 与吞吐量关系密切,故也称为 吞吐量优先收集器 用于新生代 垃圾回收,采纳 复制算法

特点:并行的多线程收集器,与 ParNew 收集器相似,然而它的指标在于达到一个可控的吞吐量。还有一个值得关注的点是 GC 自适应调节策略,这也是与 ParNew 收集器最重要的一个区别

GC 自适应调节策略 :Parallel Scavenge 收集器可设置 -XX:+UseAdaptiveSizePolicy 参数,当开关关上时不须要手动指定新生代的大小(-Xmn)、Eden 与 Survivor 区的比例(-XX:SurvivorRation)、降职老年代的对象年龄(-XX:PretenureSizeThreshold)等,虚构机会依据零碎的运行状况收集性能监控信息,动静设置这些参数以提供最优的进展工夫和最高的吞吐量,这种调节形式称为 GC 的 自适应调节策略

Parallel Scavenge 提供了两个参数用于准确管制吞吐量

  • -XX:MaxGCPauseMillis:管制最大垃圾收集进展工夫

    • 参数值是一个大于 0 的毫秒数。这个值不肯定设置的越小越好,因为零碎为了达到这个值的指标可能会把新生代空间调小些,要回收的空间小了,那么单次垃圾回收进展的工夫就小了,然而垃圾回收的频率就会减少,吞吐量就会变低
  • -XX:GCTimeRatio:设置 GC 工夫占总工夫的比率

    • 参数值是一个 0 -99 的整数。含意是,如果参数值是 19,那么 GC 工夫占总用工夫的比率就是 1/(1+19)=5%,而吞吐量就是 95%。默认参数值是 99,即 GC 占时比率为 1%,吞吐量为 99%

4.6 Parallel Old 收集器

Parallel Old 收集器是 Parallel Scavenge 老年代版本,用于老年代 垃圾回收,采纳 标记整顿算法

特点:Parallel Scavenge 收集器基本一致

利用场景:重视高吞吐量以及 CPU 资源敏感的场合,都能够优先思考 Parallel Scavenge+Parallel Old 收集器

4.7 CMS 收集器

CMS(Concurrent Mark Sweep)收集器 是一种以获取最短回收进展工夫为指标的收集器,也叫 并发收集器 用于老年代 垃圾回收,采纳 标记革除 算法。

特点:并发收集、低进展。

利用场景:实用于重视服务的响应速度,心愿零碎进展工夫最短,给用户带来更好的体验等场景下。如 web 程序、b/ s 服务。

CMS 收集器的运行过程分为下列 4 步:

  1. 初始标记 :标记 GC Roots 能间接到的对象,速度很快然而 存在 STW 问题
  2. 并发标记:进行 GC Roots Tracing 的过程,找出存活对象且用户线程可并发运行
  3. 从新标记 :为了修改并发标记期间因用户程序持续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段依然 存在 STW 问题,进展工夫个别会比初始标记长一些,但远比并发标记破费的工夫短
  4. 并发革除:清理无用对象,与用户线程并发进行

因为整个过程中耗时最长的 并发标记和并发革除过程收集器线程都能够与用户线程一起工作,所以从总体上来说,CMS 收集器的内存回收过程是与用户线程一起并发执行的

CMS 收集器的毛病:

  • 对 CPU 资源十分敏感。在并发阶段,它尽管不会导致用户线程进展,但会因为占用了一部分线程(或者说 CPU 资源)而导致应用程序变慢,总吞吐量会升高。CMS 默认启动回收线程数是 (CPU 数 +3)/4。
  • 无奈解决浮动垃圾 。并发革除阶段,用户线程也在运行就可能产生新的垃圾(未做标记 CMS 无奈革除),这一部分垃圾就被称为“浮动垃圾”。也是因为该阶段用户线程还须要运行,不能像其余垃圾收集器那样期待老年代简直被填满了再进行收集,须要预留有足够的内存空间给用户线程应用,如果预留空间有余就会 呈现 ”Concurrent Mode Failure” 失败而导致一次 Full GC(后备预案:长期启动 Serial Old 收集器进行垃圾回收)。能够通过参数-XX:CMSInitiatingOccupancyFraction 来调整老年代内存占多少百分比时触发 CMS 垃圾回收。
  • 因为采纳 标记 - 革除算法所以会存在空间碎片 的问题,导致大对象无奈调配空间,不得不提前触发一次 Full GC

4.8 G1 收集器

G1 收集器 是一款面向服务端利用的垃圾收集器,与其余垃圾回收器不同的是它的 工作范畴是整个 java 堆 ,它把整个 Java 堆 划分为多个大小相等的独立区域 (Region),尽管也保留了新生代、老年代的概念,但新生代和老年代不再是互相隔离的,他们都是一部分 Region(不须要间断) 的汇合。垃圾回收算法整体上是 标记 - 整顿 算法,两个区域 (Region) 之间是 复制算法

特点:

  • 并行与并发:G1 能充分利用多 CPU、多核环境下的硬件劣势,应用多个 CPU 来缩短 Stop-The-World 进展工夫。局部收集器本来须要进展 Java 线程来执行 GC 动作,G1 收集器依然能够通过并发的形式让 Java 程序持续运行。
  • 分代收集:G1 可能单独治理整个 Java 堆,并且采纳不同的形式去解决新创建的对象和曾经存活了一段时间、熬过屡次 GC 的旧对象以获取更好的收集成果。
  • 空间整合:G1 运作期间不会产生空间碎片,收集后能提供规整的可用内存。
  • 可预测的进展:G1 除了谋求低进展外,还能建设可预测的进展工夫模型。能让使用者明确指定在一个长度为 M 毫秒的时间段内,耗费在垃圾收集上的工夫不得超过 N 毫秒。

G1 为什么能建设可预测的进展工夫模型?

因为它有打算的防止在整个 Java 堆中进行全区域的垃圾收集。G1 跟踪各个 Region 外面的垃圾沉积的大小,在后盾保护一个优先列表,每次依据容许的收集工夫,优先回收价值最大的 Region。这样就保障了在无限的工夫内能够获取尽可能高的收集效率。

G1 收集器存在的问题:

Region 不可能是孤立的,调配在 Region 中的对象能够与 Java 堆中的任意对象产生援用关系。在采纳可达性剖析算法来判断对象是否存活时,得扫描整个 Java 堆能力保障准确性。其余收集器也存在这种问题(G1 更加突出而已)。会导致 Minor GC 效率降落。

G1 收集器是如何解决上述问题的?

采纳 Remembered Set 来防止整堆扫描。G1 中每个 Region 都有一个与之对应的 Remembered Set,虚拟机发现程序在对 Reference 类型进行写操作时,会产生一个 Write Barrier 临时中断写操作,查看 Reference 援用对象是否处于多个 Region 中(即查看老年代中是否援用了新生代中的对象),如果是,便通过 CardTable 把相干援用信息记录到被援用对象所属的 Region 的 Remembered Set 中。当进行内存回收时,在 GC 根节点的枚举范畴中退出 Remembered Set 即可保障不对全堆进行扫描也不会有脱漏。

如果不计算保护 Remembered Set 的操作,G1 收集器大抵可分为如下步骤:

  1. 初始标记:仅标记 GC Roots 能间接到的对象,并且批改 TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的 Region 中创立新对象。(须要线程进展,但耗时很短。)
  2. 并发标记:从 GC Roots 开始对堆中对象进行可达性剖析,找出存活对象。(耗时较长,但可与用户程序并发执行)
  3. 最终标记:为了修改在并发标记期间因用户程序执行而导致标记产生变动的那一部分标记记录。且对象的变动记录在线程 Remembered Set Logs 外面,把 Remembered Set Logs 外面的数据合并到 Remembered Set 中。(须要线程进展,但可并行执行。)
  4. 筛选回收:对各个 Region 的回收价值和老本进行排序,依据用户所冀望的 GC 进展工夫来制订回收打算。(可并发执行)

4.9 GC 相干参数

开启不同垃圾收集器参数

参数 形容 默认值
UseSerialGC 应用 Serial + Serial Old 的收集器组合进行内存回收 虚拟机运行在 client 模式下默认开启,其余模式默认敞开
UseParNewGC 应用 ParNew+ Serial Old 的收集器组合进行内存回收 默认敞开
UseParallelGC 应用 Parallel Scavenge + Serial Old 的收集器组合进行内存回收 虚拟机运行在 server 模式下默认开启,其余模式默认敞开
UseParallelOldGC 应用 Parallel Scavenge + Parallel Old 的收集器组合进行内存回收 默认敞开
UseConcMarkSweepGC 应用 ParNew + CMS + Serial Old 的收集器组合进行内存回收。其中 Serial Old 是当 CMS 收集呈现 ”Concurrent Mode Failuer” 时,将作为备用收集器进行 FULL GC 默认敞开
UseG1GC 应用 G1 收集器进行内存回收 默认敞开,JDK 1.9 之后版本中默认开启

其余罕用 GC 参数

参数 形容 默认值
SurvivorRatio 新生代中 Eden 区 与 Survivor 区 (其中一个) 的比值。如果等于 8,代表 Eden:Survivor=8:1 8
MaxTenuringThreshold 降职到老年代的对象年龄。每个对象经验过一次 Minor GC 后年龄就会加 1,当达到这个值时就会降职到老年代 15
PretenureSizeThreshold 能够间接降职老年代的对象大小。如果设置了这个参数值后,大小超过这个值的对象间接降职到老年代,不须要等到年龄达到 MaxTenuringThreshold 值。
ScavengeBeforeFullGC 在 FULL GC 之前是否触发一次 Minor GC 默认开启
DisableExplicitGC 疏忽手动调用 System.gc() 触发的垃圾回收。即若开启,则代码中调用 System.gc() 不起作用 默认敞开
ParallelGCThreads 设置并行 GC 进行垃圾回收的线程数。此参数对于 CMS(并发收集器)也无效 默认值随 JVM 所运行的平台而变动,个别与 CPU 数目相等
ConcGCThreads 设置并发 GC 进行垃圾回收的线程数 默认值随 JVM 所运行的平台而变动,个别是 ParallelGCThreads 的四分之一
UseAdaptiveSizePolicy 动静调整 java 对中各个区域的大小及进入老年代的年龄。当开关关上时不须要手动指定 -Xmn、-XX:SurvivorRation、-XX:PretenureSizeThreshold 等参数,虚构机会依据零碎的运行状况收集性能监控信息,动静设置这些参数以提供最优的进展工夫和最高的吞吐量,这种调节形式称为 GC 的 自适应调节策略 默认开启
GCTimeRatio 设置 GC 工夫占总工夫的比率。如果参数值是 99,那么 GC 工夫占总用工夫的比率就是 1/(1+99)=1%。该参数只对 Parallel Scavenge 收集器无效 99
MaxGCPauseMillis 设置最大 GC 最大暂停工夫的。这是一个软指标,JVM 将尽最大努力实现它。该参数只对 Parallel Scavenge 收集器无效
CMSInitiatingOccupancyFraction 设置 CMS 收集器在老年代空间被应用多少后触发垃圾收集,参数值在 0~100 之间。该参数只对 CMS 收集器无效 默认值为 -1,即如果不设置参数则应用公式计算得来
UseCMSCompactAtFullCollection 设置 CMS 收集器在实现垃圾收集后是否要进行一次内存碎片整顿(CMS 采纳的标记革除算法因而会产生碎片)。该参数只对 CMS 收集器无效 默认开启
CMSFullGCsBeforeCompaction 在 UseCMSCompactAtFullCollection 开启的状况下,设置 CMS 进行多少次垃圾回收后再启动一次内存碎片整顿。该参数只对 CMS 收集器无效 0
CMSScavengeBeforeRemark 在 CMS 收集的从新标记阶段之前,是否进行一次 Minor GC(可在肯定水平上缩小从新标记阶段对象的扫描)。该参数只对 CMS 收集器无效 默认敞开
G1HeapRegionSize 设置 G1 收集器每个 Region 区域大小。该参数只对 G1 收集器无效 这个参数的默认值是依据堆大小确定的,个别是堆内存大小的 1 /2000。最小值是 1Mb,最大值是 32Mb

应用 串行 收集器罕用参数组合:

-XX:+UseSerialGC ~ Serial + SerialOld  

应用 吞吐量优先 收集器罕用参数组合:

-XX:+UseParallelGC ~ -XX:+UseParallelOldGC 
-XX:+UseAdaptiveSizePolicy
-XX:GCTimeRatio=ratio
-XX:MaxGCPauseMillis=ms
-XX:ParallelGCThreads=n

应用 响应工夫优先 收集器罕用参数组合:

-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld
-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads
-XX:CMSInitiatingOccupancyFraction=percent
-XX:+CMSScavengeBeforeRemark

启用 G1 收集器 参数组合:

-XX:+UseG1GC
-XX:G1HeapRegionSize=size
-XX:MaxGCPauseMillis=time

4.10 GC 日志剖析

打印 GC 日志的罕用参数

参数 形容 默认值
-XX:+PrintGC 打印 GC 日志。(与规范参数 -verbose:gc 作用统一) 敞开
-XX:+PrintGCDetails 打印具体的 GC 日志,还会在退出前打印堆的详细信息 敞开
-XX:+PrintGCTimeStamps 打印 GC 产生的工夫。从虚拟机启动以来经验的秒数,如 100.667 敞开
-XX:+PrintGCDateStamps 打印 GC 产生的工夫。日期模式,如 2020-10-11T20:01:53.769+0800 敞开
-XX:+PrintGCApplicationConcurrentTime 打印应用程序的执行工夫 敞开
-XX:+PrintGCApplicationStoppedTime 打印利用因为 GC 而产生的进展工夫 敞开
-XX:+PrintHeapAtGC 每次 GC 前后打印堆信息 敞开
-Xloggc:<filename> 将 GC 日志以文件模式输入。如:-Xloggc:../logs/gc.log

每一种垃圾收集器的日志都有稍微不同,然而格局上都根本都是雷同的。以上面示例代码为例,查看 GC 日志

示例代码:

package indi.taicw.jvm.gc;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * -Xms20M -Xmx20M -Xmn10M -XX:+UseParallelGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
 * 其中 -XX:+UseParallelGC 是 JDK1.8 默认就启用的,也能够省略不写
 */
public class GCDemo04 {public static void main(String[] args) {List<String> list = new ArrayList<>();
        for (int i = 0; i < 100000; i++) {String s = String.valueOf(new Random().nextInt());
            if (i%2 == 0) {list.add(s);
            }
        }
        
        list = null;
        System.gc();}
}

输入后果:

  • :GC 产生的工夫,从虚拟机启动以来经验的秒数
  • :格局:GC 类型 (GC 产生起因)

    • GC 类型:”GC” 示意 Minor GC,个别只清理新生代;”Full GC” 示意 Full GC,个别清理整个 java 堆,包含元空间
    • GC 产生起因:”Allocation Failure” 调配失败、”System.gc()” 手动触发 等等起因
  • 区域: 回收前该区域已应用空间大小 -> 回收后该区域已应用空间大小(该区域总大小)

    • 区域:同一区域不同的垃圾收集器示意的关键字可能不同,比方新生代关键字 当开启 -XX:+UseParallelGC 时为 ”PSYoungGen”,当开启 -XX:+UseSerialGC 时为 ”DefNew”。
    • JDK 1.8 之后移除了永恒代区域 “PSPermGen”,取而代之的是元空间区域 ”Metaspace”,元空间区域并不占用 java 内存而是占用本地内存
  • 回收前整个 java 堆已应用空间大小 -> 回收后整个 java 堆已应用空间大小(java 总大小)
  • :本次垃圾回收消耗工夫
  • :这外面的 user、sys、和 real 与 Linux 的 time 命令所输入的工夫含意统一,别离代表用户耗费的 CPU 工夫,内存态耗费的 CPU 工夫,和操作从开始到完结所通过的墙钟工夫
  • :堆信息。启用 -XX:+PrintGCDetails 参数,程序会在退出前打印以后堆信息,别离展现了新生代、老年代、元空间区域内存应用状况

下面的例子中启动参数 -Xmn10M 设置了新生代总大小是 10M,然而 GC 日志中显示的新生代总大小是 9216K(即 9M),这是因为幸存区 TO 的空间是用来垃圾回收时直达对象的,其余工夫都是空着的,所以计算新生代总大小时只蕴含 Eden 区和幸存区 FROM

4.11 GC 调优

没有放眼四海皆可用的调用计划,须要依据零碎运行平台和运行环境进行调优。

调优畛域

  • 内存
  • 锁竞争
  • CPU 占用
  • IO

确定指标

【低提早】还是【高吞吐量】?抉择适合的垃圾收集器

最快的 GC 是???
答案是不产生 GC。首先排除缩小因为本身编写的代码而引发的内存问题。查看 Full GC 前后的内存占用,思考以下几个问题:

  • 数据是不是太多?

    • 比方查询数据库大表,会把所有数据都加载到内存
  • 数据表示是否太臃肿

    • 对象图
    • 对象大小
  • 是否存在内存透露

    • 比方动态 Map 中一直增加元素,从不移除元素


新生代调优

  • 新生代特点

    • 所有的 new 操作分配内存都是十分便宜的

      • TLAB(thread-local allocation buffer)
    • 死亡对象回收零代价
    • 大部分对象用过即死(朝生夕死)
    • Minor GC 所用工夫远小于 Full GC
  • 新生代内存越大越好么?不是

    • 新生代内存太小:频繁触发 Minor GC,会 STW,会使得吞吐量降落
    • 新生代内存太大:老年代内存占比有所升高,会更频繁地触发 Full GC。而且触发 Minor GC 时,清理新生代所破费的工夫会更长
  • 新生代能包容所有【并发量 * (申请 - 响应)】的数据
  • 幸存区大到能保留【以后沉闷对象 + 须要降职对象】
  • 降职阈值配置切当,让长时间存活对象尽快降职

老年代调优

以 CMS 为例

  • CMS 的老年代内存尽量大些

    • 内存过小,并发清理阶段产生浮动垃圾可能导致并发失败而触发 FULL GC
  • 先尝试不做调优,如果没有 Full GC 阐明老年代曾经比拟优化了,即便有 Full GC 也应先尝试调优新生代
  • 察看产生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3

    • -XX:CMSInitiatingOccupancyFraction=percent

案例剖析

  • 案例 1 Full GC 和 Minor GC 频繁

    • 可能是新生代内存缓和,导致年龄很小的对象提前进入了老年代,导致老年代内存也有余了
    • 解决:适当调整新生代内存大小,使存活很短的对象不要进入老年代
  • 案例 2 申请高峰期产生 Full GC,并且单次暂停工夫特地长(CMS)

    • 可能是因为在 CMS 并发标记阶段用户线程新创建的对象比拟多,导致从新标记阶段须要扫描的对象增多因而进展工夫增多;并发清理阶段产生的浮动垃圾比拟多导致 FULL GC
    • 解决:在从新标记之前进行一次 Minor GC,-XX:+CMSScavengeBeforeRemark
  • 案例 3 老年代富余状况下,产生 Full GC(CMS jdk1.7)

    • 可能是永恒代内存不足了,导致了 FULL GC
    • JDK 1.7 永恒代也占用的是 java 堆内存,所以也会导致 FULL GC;JDK 1.8 之后,移除了永恒代取而代之的是元空间,应用的是本地内存,不会导致 FULL GC
退出移动版