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()返回nullsoftRef.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:18
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