JVM学习03:垃圾回收

本文介绍了JVM中的垃圾回收原理,包括引用计数法和可达性分析两种判断对象可回收的方法,以及强引用、软引用、弱引用和虚引用四种类型的引用。接着,详细阐述了标记清除、标记整理和复制三种垃圾回收算法,以及分代垃圾回收策略。此外,还讨论了不同场景下的垃圾回收器选择,如串行、吞吐量优先和响应时间优先的收集器,以及G1垃圾回收器的工作机制。最后,提到了一些垃圾回收调优的思路和案例。
摘要由CSDN通过智能技术生成

JVM学习03:垃圾回收

1、如何判断对象可以回收

1.1、引用计数法

  • 记录当前对象被引用的次数,当引用次数为0时则进行垃圾回收。
  • 缺点:当两个对象互相引用但并没有其他对象再引用它们时,他们的引用次数都为1,无法对其进行回收释放。如图所示:

在这里插入图片描述

  • 早期的python使用这种方法,但是java而是采用可达性分析算法。

1.2、可达性分析算法

  • Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象。

  • 扫描堆中的对象,看是否能够沿着GC Root对象为起点的引用链找到该对象,找不到,表示可以回收。

  • 哪些对象可以作为 GC Root ?

    • System Class:系统类,例如Object类、HashMap类等。
    • Native Stack:本地方法栈中引用的java对象。
    • Thread:活动线程中使用的对象,即栈帧中的局部变量等引用的对象。
    • Busy Monitor:所有被同步锁(synchronized关键字)持有的对象。

1.3、四种引用

在这里插入图片描述

注意:图中实现为强引用,虚线为其他引用。

强引用

  • 只有所有 GC Roots 对象都不通过强引用引用该对象,该对象才能被垃圾回收。
  • 例如图中A1对象,当B、C对象都不引用它时才会被垃圾回收。

软引用(SoftReference)

  • 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会回收软引用引用的对象,回收软引用对象可以配合引用队列来释放软引用自身占用的内存。
  • 例如图中A2对象,当B对象不再引用A2对象,并且垃圾回收后内存不足时,软引用所引用的A2对象会被回收。但是软引用自身没有被清理,可以使用引用队列进行处理,释放软引用占用的内存。

测试代码1:

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

/**
 * 演示软引用
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 */
public class demo02 {

    private static final int _4MB = 4 * 1024 * 1024;

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

    // list --> SoftReference --> byte[]
    public static void soft() {
        List<SoftReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());
        }
        System.out.println("循环结束:" + list.size());
        for (SoftReference<byte[]> ref : list) {
            System.out.println(ref.get());
        }
    }

}

测试结果:

可以看出,软引用对象(4MB的byte数组)在垃圾回收后,内存不足时进行清理。

在这里插入图片描述

测试代码2:引用队列

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 demo03 {
    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());
        }

    }
}

测试结果:

使用引用队列后,原来结果中的软引用对象(4个null)被去除掉了。

弱引用(WeakReference)

  • 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用引用的对象,可以配合引用队列来释放弱引用自身占用的内存。
  • 例如图中A3对象,当B对象不再引用A3对象,只要进行垃圾回收,A3对象就会被清理。和软应用一样,可以使用引用队列释放弱引用自身占用的内存。

测试代码:

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

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

    public static void main(String[] args) {
        //  list --> WeakReference --> byte[]
        List<WeakReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB]);
            list.add(ref);
            for (WeakReference<byte[]> w : list) {
                System.out.print(w.get()+" ");
            }
            System.out.println();

        }
        System.out.println("循环结束:" + list.size());
    }
}

测试结果:

当垃圾回收时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

在这里插入图片描述

虚引用(PhantomReference)

  • 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存。

  • 例如图中的ByteBuffer对象,当ByteBuffer对象实力创建时,会创建一个虚引用对象Cleaner来引用它,这时会分配一块直接内存,并且会把直接内存地址传递给Cleaner对象,当ByteBuffer对象被清理时,虚引用对象Cleaner会放入引用队列,当 ReferenceHandler 线程监测到有对象进入队列时,会调用相关方法释放直接内存。(上一节直接内存中讲过)

在这里插入图片描述

在这里插入图片描述

终结器引用(FinalReference)

  • 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize方法,第二次 GC 时才能回收被引用对象。
  • 例如图中的A4对象。所有的类都继承自Object类,Object类有一个finalize方法。当A4对象重写了finalize()方法后,此对象不再被其他的对象所强引用时,JVM会创建一个终结器引用,垃圾回收时会先将终结器引用对象放入引用队列中,然后Finalizer 线程会查看引用队列,若其中有终结器引用,则会通过终结器引用找到它所引用的对象并调用该对象的finalize方法。调用以后,再进行一次GC,该对象才会被垃圾回收。
  • 不推荐使用finalize()方法进行内存释放,其条件太复杂了。

2、垃圾回收算法

2.1、标记清除

定义: Mark Sweep

  • 算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。

  • 注意:这里的清理出来的内存空间并不是将内存空间的字节清0,而是记录下这段内存的起始和结束地址,下次分配内存的时候,会直接覆盖这段内存。

  • 优点:速度较快。

  • 缺点:会造成内存碎片。

在这里插入图片描述

2.2、标记整理

定义:Mark Compact

  • 先采用标记算法确定可回收对象,然后将让所有存活的对象都向内存空间一端移动,最后直接清理掉边界以外的内存。

  • 优点:没有内存碎片。

  • 缺点:速度慢。

在这里插入图片描述

2.3、复制

定义:Copy

  • 它将可用内存按容量划分为大小相等的两个区域:from区和to区。当from内存满了的时候,首先标记存活的对象,然后把存活的对象从from区复制到to区,然后将from区清空,最后交换from区和to区。

  • 优点:不会有内存碎片。

  • 缺点:需要占用双倍内存空间。

在这里插入图片描述

3、分代垃圾回收

在这里插入图片描述

  • 对象首先分配在伊甸园区域。

  • 新生代空间不足时,触发 minor gc,伊甸园和 from 存活的对象使用 copy 复制到 to 中,存活的对象年龄加 1,并且交换 from to。

  • minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行。

  • 当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)。

  • 当老年代空间不足,会先尝试触发 minor gc,如果之后空间仍不足,那么触发 full gc,STW的时间更长。

  • 新生代一般采用复制算法,老年代一般使用标记整理算法。

  • 如果放入一个大对象,新生代放不进去时,会直接晋升到老年代,不需要引发GC了。

3.1、相关 VM 参数

含义参数
堆初始大小-Xms
堆最大大小-Xmx 或 -XX:MaxHeapSize=size
新生代大小-Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size )
幸存区比例(动态)-XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy
幸存区比例-XX:SurvivorRatio=ratio
晋升阈值-XX:MaxTenuringThreshold=threshold
晋升详情-XX:+PrintTenuringDistribution
GC详情-XX:+PrintGCDetails -verbose:gc
FullGC 前 MinorGC-XX:+ScavengeBeforeFullGC

测试代码:

package com.jvm.lesson03;

import java.util.ArrayList;

/**
 *  演示内存的分配策略
 */
public class demo05 {
    private static final int _512KB = 512 * 1024;
    private static final int _1MB = 1024 * 1024;
    private static final int _6MB = 6 * 1024 * 1024;
    private static final int _7MB = 7 * 1024 * 1024;
    private static final int _8MB = 8 * 1024 * 1024;

    // -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -XX:-ScavengeBeforeFullGC
    public static void main(String[] args) throws InterruptedException {
        /*ArrayList<byte[]> list = new ArrayList<>();
        list.add(new byte[_7MB]);
        list.add(new byte[_512KB]);
        list.add(new byte[_512KB]);*/

        //当一个线程抛出OOM异常后,不会影响其他线程的运行。
        new Thread(() -> {
            ArrayList<byte[]> list = new ArrayList<>();
            list.add(new byte[_8MB]);//如果放入一个大对象,新生代放不进去时,会直接晋升到老年代,不需要引发GC了。
            list.add(new byte[_8MB]);
        }).start();

        System.out.println("sleep....");
        Thread.sleep(1000L);
    }
}

结果分析:

  • 如果放入一个大对象,新生代放不进去时,会直接晋升到老年代,不需要引发GC了。
  • 当一个线程抛出OOM异常后,不会影响其他线程的运行。

4、垃圾回收器

这部分结合书《深入理解java虚拟机第三版》看。

在这里插入图片描述

4.1、串行

  • 单线程
  • 堆内存较小,适合个人电脑
  • 参数:
    • -XX:+UseSerialGC:开启 Serial + SerialOld 收集器。

在这里插入图片描述

  • 串行的收集器比如 SerialSerialOld 收集器。Serial收集器工作在新生代,采用复制算法;SerialOld收集器工作在老年代,采用标记整理算法。

4.2、吞吐量优先

  • 多线程
  • 堆内存较大,多核 cpu
  • 单位时间内,STW 的时间最短,垃圾回收时间占比最低,这样就称吞吐量高。
  • 参数:
    • -XX:+UseParallelGC ~ -XX:+UseParallelOldGC :分别为开启新生代和老年代的并行垃圾回收器,开启一个另一个自动开启。
    • -XX:+UseAdaptiveSizePolicy :使用自适应的调整策略,来调整新生代的大小、伊甸园区和幸存去的比例、晋升老年代的阈值等。
    • XX:GCTimeRatio=ratio :根据 1/(1+ratio)(垃圾收集时间占总时间的比率) 尝试调整堆的大小,进而调整吞吐量的大小。例如把此参数设置为19,那允许的最大垃圾收集时间就占总时间的5%(即1/(1+19))。
    • -XX:MaxGCPauseMillis=ms :最大垃圾回收暂停的毫秒数,默认为200ms。
    • -XX:ParallelGCThreads=n:指定并行垃圾回收线程数,一般与CPU核数相同。

在这里插入图片描述

  • 吞吐量:就是处理器用于运行用户代码的时间与处理器总消耗时间的比值。

    吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾回收时间)

  • 吞吐量优先的收集器比如 Parallel ScavengeParallel Old 收集器。Parallel Scavenge收集器工作在新生代,采用复制算法;Parallel Old收集器工作在老年代,采用标记整理算法。

4.3、响应时间优先

  • 多线程
  • 堆内存较大,多核 cpu
  • 尽可能让单次 STW 的时间最短。
  • 参数:
    • -XX:+UseConcMarkSweepGC:开启并发的使用标记清除算法的垃圾回收器。
    • -XX:ParallelGCThreads=n :设置并行的垃圾回收线程数,一般与CPU核数相同。
    • -XX:ConcGCThreads=threads :设置并发的垃圾回收线程数,一般设置为并行垃圾回收线程数的1/4。
    • -XX:CMSInitiatingOccupancyFraction=percent :设置CMS垃圾回收触发的百分比。当老年代使用的内存占比到了这个数值后才会触发立即回收,预留出一定的空间给其他用户线程和浮动垃圾(并发清理时新产生的垃圾)。
    • -XX:+CMSScavengeBeforeRemark:在重新标记阶段前对新生代进行一次垃圾回收,减少时间浪费。因为在重现标记阶段会扫描整个堆,从新生代查找其引用老年代的对象做可达性分析,但新生代的对象非常多且其中包含很多并发标记阶段产生的垃圾,这就造成浪费大量的时间查找无用的引用。

在这里插入图片描述

  • 并行和并发的区别:
    • 并行是指两个或者多个事件在同一时刻发生,而并发是指两个或多个事件在同一时间间隔发生。
    • 并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
    • 并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。
  • 响应时间优先的收集器例如CMS收集器,它工作在老年代,使用标记清除算法实现。一般在新生代配合ParNew(-XX:+UseParNewGC)收集器使用。由于CMS收集器在老年代使用的标记清除算法,会产生大量的内存碎片,当预留的内存无法满足程序分配新对象的需要时,导致并发失败,这时会使用 SerialOld 收集器作为补救措施。

4.4、G1

定义:Garbage First

  • 2004 论文发布
  • 2009 JDK 6u14 体验
  • 2012 JDK 7u4 官方支持
  • 2017 JDK 9 默认

适用场景:

  • 同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停目标是 200 ms。
  • 超大堆内存,会将堆划分为多个大小相等的Region,每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。
  • 整体上是标记+整理算法,两个区域之间是复制算法。

相关 JVM 参数:

-XX:+UseG1GC :开启G1收集器。

-XX:G1HeapRegionSize=size :每个Region的大小。

-XX:MaxGCPauseMillis=time:设定允许的收集停顿时间,默认为200ms。

4.1.1、G1垃圾回收阶段

在这里插入图片描述

4.2.2、Young Collection

  • 会 STW
  • 对象首先放到伊甸园区中,当伊甸园(E)的空间占满时,会触发Young Collection,将伊甸园的幸存对象以复制算法放入幸存区(S)。当幸存区的空间快要占满或幸存区对象年龄达到一定阈值,会再次触发Young Collection,幸存区的部分对象会晋升至老年代,年龄不够的会复制到另一块幸存区。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.2.3、Young Collection + CM

  • 在 Young GC 时会进行 GC Root 的初始标记(会 STW)

  • 老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW),由下面的 JVM 参数决定:

    -XX:InitiatingHeapOccupancyPercent=percent (默认45%)

在这里插入图片描述

4.2.4、Mixed Collection

会对 E、S、O 进行全面垃圾回收。

  • 最终标记(Remark)会 STW

  • 拷贝存活(Evacuation)会 STW

-XX:MaxGCPauseMillis=ms:设定允许的收集停顿时间,默认为200ms。使得在有限的时间里优先回收老年代中回收价值较高的(垃圾多的)。

在这里插入图片描述

4.2.5、Full GC

  • SerialGC

    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足发生的垃圾收集 - full gc
  • ParallelGC

    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足发生的垃圾收集 - full gc
  • CMS

    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足 - 当老年代使用的内存占比到了设定的百分比会进行并发收集。当内存碎片足够多时导致新的对象放入时内存不足,会造成并发失败,并发收集失败时退化为串行收集,此时才会进行full gc
  • G1

    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足 - 老年代占用堆空间比例达到阈值(默认45%)时,进行并发标记(不会 STW)与混合收集,当回收速度快于垃圾产生速度时会进行并发收集。只有当垃圾产生速度快于回收速度时并发收集失败,退化为串行收集,此时才会进行full gc。

4.2.6、Young Collection 跨代引用

新生代回收的跨代引用(老年代引用新生代)问题:

在进行Young Collection时,首先要找到GC Root根对象,根据其进行可达性分析找到存活的对象复制入幸存区。但根对象一部分是来自老年代的,且老年代的存活对象较多,对老年代进行遍历查找效率很低。因此采用卡表技术对老年代进行分区,每个card大小约为512k。若此card中有对象引用了新生代的对象,则将其标记为脏卡。这时新生代会有一个 Remembered Set来存放被标记的脏卡,因此在Young Collection查找根对象时只需要根据 Remembered Set来查找脏卡即可,提高效率。

在引用变更时通过 post-write barrier + dirty card queue的方法来重新标记脏卡,这是一个异步操作, Remembered Set可以通过concurrent refinement threads来更新。

在这里插入图片描述

在这里插入图片描述

4.2.7、Remark(重新标记)

  • pre-write barrier + satb_mark_queue

在这里插入图片描述

图中:黑色为已被处理的;灰色为正在处理中的;白色为还未处理的。

如果对象的引用发生改变,JVM就会给其加入写屏障pre-write barrier,并执行写屏障指令,将C加入队列中 satb_mark_queue,并把C标记为灰色。并发标记结束后,进入重新标记阶段,此时会STW,并将satb_mark_queue中的对象一个个取出来进行检查,如此对象有强引用引用时,将其修改为黑色,进行对象保留,否则被修改成白色进行清理。
在这里插入图片描述

4.2.8、JDK 8u20 字符串去重

  • 优点:节省大量内存。

  • 缺点:略微多占用了 cpu 时间,新生代回收时间略微增加。

  • 参数:-XX:+UseStringDeduplication(默认是打开的)

//连个字符串对象引用同一个字符数组
String s1 = new String("hello"); // char[]{'h','e','l','l','o'} 
String s2 = new String("hello"); // char[]{'h','e','l','l','o'}
  • 将所有新分配的字符串放入一个队列。

  • 当新生代回收时,G1并发检查是否有字符串重复。

  • 如果它们值一样,让它们引用同一个 char[]。

  • 注意,与 String.intern() 不一样。

    • String.intern() 关注的是字符串对象
    • 而字符串去重关注的是字符串对象引用的 char[]
  • 在 JVM 内部,使用了不同的字符串表。

4.2.9、JDK 8u40 并发标记类卸载

  • 所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器(一般为自定义加载器)的所有类都不再使用,则卸载它所加载的所有类。

  • 参数:-XX:+ClassUnloadingWithConcurrentMark (默认启用)

4.2.10、JDK 8u60 回收巨型对象

  • 一个对象大于 region 的一半时,称之为巨型对象。

  • G1 不会对巨型对象进行拷贝。

  • 回收时被优先考虑。

  • G1 会跟踪老年代所有 incoming 引用(脏卡引用巨型对象的次数),这样老年代 incoming 引用为0 的巨型对象就可以在新生代垃圾回收时处理掉。

在这里插入图片描述

4.2.11、JDK 9 并发标记起始时间的调整

  • 并发标记必须在堆空间占满前完成,否则退化为 FullGC。

  • JDK 9 之前需要使用: -XX:InitiatingHeapOccupancyPercent(默认45%)

  • JDK 9 可以动态调整:

    • -XX:InitiatingHeapOccupancyPercent用来设置初始值。
    • 进行数据采样并动态调整。
    • 总会添加一个安全的空档空间。

4.2.12、JDK 9 更高效的回收

  • 250+增强

  • 180+bug修复

  • https://docs.oracle.com/en/java/javase/12/gctuning

5、垃圾回收调优

预备知识:

  • 掌握 GC 相关的 VM 参数,会基本的空间调整。

    查看虚拟机的运行参数:"java的路径" -XX:+PrintFlagsFinal -version | findstr "GC"

在这里插入图片描述

  • 掌握相关工具:jmap、jconsole、VisualVM等。

  • 明白一点:调优跟应用、环境有关,没有放之四海而皆准的法则。

5.1、调优领域

  • 内存

  • 锁竞争

  • cpu 占用

  • io

5.2、确定目标

  • 【低延迟】还是【高吞吐量】,选择合适的回收器。

  • 低延迟的虚拟机:CMS,G1,ZGC

  • 高吞吐量的虚拟机:ParallelGC

  • 其他的的虚拟机:Zing

5.3、最快的 GC

答案是不发生 GC。

查看 FullGC 前后的内存占用,考虑下面几个问题:

  • 数据是不是太多?

    • 数据库里读取数据:resultSet = statement.executeQuery("select * from 大表 limit n")。可以后面加上limit n
  • 数据表示是否太臃肿?

    • 对象图
    • 对象大小 :比如Integer占24个字节,int占4个字节,尽量使用基本类型。
  • 是否存在内存泄漏?

    • 把数据全放入一个map中,垃圾得不到即使清理,会导致内存泄漏。
    • 可以使用软引用、弱引用或者第三方缓存实现,比如redis。

5.4、新生代调优

  • 新生代的特点:

    • 所有的 new 操作的内存分配非常廉价。

      • TLAB(thread-local allocation buffer):线程分布局部缓冲区,作用是让每个线程用自己私有的内存进行对象分配,因此多个线程同时创建对象时不会产生内存冲突。
    • 死亡对象的回收代价是零(一般使用复制算法)。

    • 大部分对象用过即死。

    • Minor GC 的时间远远低于 Full GC。

  • 新生代内存越大越好吗?

-Xmn参数:
设置新生代的初始和最大内存。在这个区域执行GC的频率要高于其他区域。如果新生代的内存太小,那么就会执行大量的minor GC。如果内存太大,那么就会执行full GC,full GC可能需要很长的时间来完成。Oracle建议你将年轻一代的大小保持在整个堆大小的25%以上,50%以下。

补充:吞吐量随着新生代内存增大先增大再减小,一开始内存增大后,垃圾回收频率变低,吞吐量增加;到了一定阶段,内存太大时,垃圾回收占的时间也增加,此时吞吐量开始减小。

  • 新生代内存估算:【并发量 * (请求-响应)】(每个请求相应占用的内存量*并发数)。

  • 幸存区大到能保留**【当前活跃对象+需要晋升对象】**。幸存区太小会导致一部分存活时间较短的对象晋升至老年代,老年代对象full gc时才会回收,进而造成内存浪费。

  • 晋升阈值配置得当,让长时间存活对象尽快晋升,否则就会在幸存区反复被复制。

    -XX:MaxTenuringThreshold=threshold :设置最大晋升阈值。

    -XX:+PrintTenuringDistribution:打印晋升区存活对象的详情。

Desired survivor size 48286924 bytes, new threshold 10 (max 10) 
- age 1: 28992024 bytes, 28992024 total 
- age 2: 1366864 bytes, 30358888 total 
- age 3: 1425912 bytes, 31784800 total 
...

5.5、老年代调优

以 CMS 为例:

  • CMS 的老年代内存越大越好。预留更多的空间,避免浮动垃圾造成并发失败。

  • 先尝试不做调优,如果没有 Full GC ,那先尝试调优新生代。

  • 观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3。

  • -XX:CMSInitiatingOccupancyFraction=percent:设置CMS垃圾回收触发的百分比。

5.6、案例

案例1:Full GC 和 Minor GC 频繁。

可能是新生代内存太小引发的Minor GC 频繁,可以尝试增大新生代的内存大小;然后可以适当的增大晋升老年代的阈值,防止Full GC频繁。

案例2:请求高峰期发生 Full GC,单次暂停时间特别长(CMS)。

CMS垃圾回收器在初始标记和并发标记消耗的时间较短,但是重新标记消耗的时间较长,因为他需要重新扫描老年代和新生代。可以使用-XX:+CMSScavengeBeforeRemark参数在重新标记阶段前对新生代进行一次垃圾回收,减少时间浪费。

案例3:老年代充裕情况下,发生 Full GC(CMS jdk1.7)。

可能是永久代内存不足导致的Full GC。在 JDK 1.7 及以前,JVM中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值