JVM-垃圾回收

本文详细介绍了Java垃圾回收的原理,包括引用计数法、可达性分析算法以及四种引用类型。讨论了各种垃圾回收算法的优缺点,如标记清除、标记整理和复制算法。此外,还讲解了分代垃圾回收策略,新生代与老年代的管理,并探讨了G1垃圾收集器的工作机制。文章最后分享了一些垃圾回收的调优案例和建议,强调了内存分配、锁竞争、CPU占用和IO在调优中的重要性。
摘要由CSDN通过智能技术生成

垃圾回收

如何判断对象可以回收

引用计数法

  • 当一个对象被其他变量引用,该对象计数加一,当某个变量不在引用该对象,其计数减一
  • 当一个对象引用没有被其他变量引用时,即计数变为0时,该对象就可以被回收

缺点:循环引用时,两个对象的计数都是1,导致两个对象都无法被释放

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-evIq5kGt-1648738836376)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220324213933096.png)]

可达性分析算法

  • JVM中的垃圾回收器通过可达性分析来探索所有存活的对象
  • 扫描堆中的对象,看能否沿着GC Root对象为起点的引用链找到该对象,如果找不到,则表示可以回收
  • 可以作为GC Root的对象
    • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中JNI(即一般说的Native方法)引用的对象
    • 所有被同步锁(synchronized关键字)持有的对象。

四种引用

  1. 强引用

    • 只有所有GC Roots对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
  2. 软引用

    • 仅有【软引用】引用该对象时,在垃圾回收后,内存仍不足时会再次发出垃圾回收。
    • 可以配合【引用队列】来释放软引用自身(不是一定的)
  3. 弱引用

    • 仅有【弱引用】引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
    • 可以配合【引用队列】来释放弱引用自身(不是一定的)
  4. 虚引用

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

    • 如上图,B对象不再引用ByteBuffer对象,ByteBuffer就会被回收。但是直接内存中的内存还未被回收。这时需要将虚引用对象Cleaner放入引用队列中,然后调用它的clean方法来释放直接内存

  5. 终结器引用

    • 无需手动编码,但其内部配合【引用队列】使用,在垃圾回收时,【终结器引用】入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过【终结器引用】找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象
    • 如上图,B对象不再引用A4对象。这时终结器对象就会被放入引用队列中,引用队列会根据它,找到它所引用的对象。然后调用被引用对象的finalize方法。调用以后,该对象就可以被垃圾回收了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nPo4u0c4-1648738836377)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220324221845913.png)]

  • 软引用的使用
public class Demo1 {
	public static void main(String[] args) {
		final int _4M = 4*1024*1024;
		//使用软引用对象 list和SoftReference是强引用,而SoftReference和byte数组则是软引用
		List<SoftReference<byte[]>> list = new ArrayList<>();
		SoftReference<byte[]> ref= new SoftReference<>(new byte[_4M]);
	}
}
  • 软引用—引用队列
public static void main(String[] args) throws IOException {
    ///使用引用队列,用于移除引用为空的软引用对象
    ReferenceQueue<byte[]> queue=new ReferenceQueue<>();
	//使用软引用对象 list和SoftReference是强引用,而SoftReference和byte数组则是软引用
    List<SoftReference<byte[]>> list = new ArrayList<>();
    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("=============");
   
    System.out.println("循环结束:" + list.size());
    for (SoftReference<byte[]> ref : list) {
        System.out.println(ref.get());
    }
}
  • 弱引用

    • 弱引用的使用和软引用类似,只是将 SoftReference 换为了 WeakReference
    public static void main(String[] args) {
        //使用弱引用对象 list和SoftReference是强引用,而SoftReference和byte数组则是弱引用
        List<WeakReference<byte[]>> list=new ArrayList<>();
        for (int i = 0; i < 5; 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());
    }
    

垃圾回收算法

标记清除

  • 定义(Mark Sweep):在虚拟机执行垃圾回收的过程中,先采用标记算法确定可回收对象,然后垃圾收集器根据标识清除相应的内容,给堆内存腾出相应的空间

  • 注意:这里的清除并不是将内存空间字节清零,而是记录这段内存的起始地址,下次分配内存的时候,会直接覆盖这段内存

  • 优点:速度快

  • 缺点:容易产生内存碎片。一旦分配较大内存的对象,由于内存不连续,导致无法分配,最后造成了内存溢出的问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KZPC634a-1648738836378)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328211238700.png)]

标记整理

定义(Mark Compact):在虚拟机执行垃圾回收的过程中,先采用标记算法确定可回收对象,然后整理剩余的对象,将可用的对象移动到一起,使内存更加紧凑,连续的空间就更多。

优点:不会有内存碎片

缺点:速度慢

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lUnfBaMI-1648738836378)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328211952510.png)]

复制

定义(Copy):将内存分为等大小的两个区域FROMTO(TO中为空)。将被GC Root引用的对象从FROM放入TO中,再回收不被GC Root引用的对象。然后交换FROM和TO。这样也可以避免内存碎片的问题,但是会占用双倍的内存空间

优点:不会有内存碎片

缺点:会占用双倍的内存空间。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4qbPvP7s-1648738836378)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328212441390.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w5p1MsSG-1648738836379)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328212451909.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2y0wG4sZ-1648738836379)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328212459890.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nvMmwAOK-1648738836379)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328212507460.png)]

分代垃圾回收

定义

  • 将堆内存分为新生代老年代,新生代又划分为伊甸园幸存区To幸存区From

    • 新生代:用完了就可以丢弃,经常发生垃圾回收。
    • 老年代:长时间使用的程序,很长时间发生一次回收。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uq5Pb4Kx-1648738836380)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328213247191.png)]

回收流程

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X19ZdCUU-1648738836380)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328213919159.png)]

  • 伊甸园空间不足时,触发 Minor GC,伊甸园和 from 存活的对象使用 copy 复制到 to 中,存活的对象年龄加 1并且交换 幸存区from和 幸存区to

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ciSSGqr4-1648738836381)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328214014405.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tNm7p14v-1648738836381)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328214036558.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9fVfoPdr-1648738836382)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328214044669.png)]

  • 再次创建对象,若新生代的伊甸园又满了,则会再次触发 Minor GC(minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行),这时不仅会回收伊甸园中的垃圾,还会回收幸存区中的垃圾,再将活跃对象复制到幸存区TO中。回收以后会交换两个幸存区,并让幸存区中的对象寿命加1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oGqViGEC-1648738836382)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328214218520.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mppovT5W-1648738836383)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328214241108.png)]

  • 当老年代空间不足,会先尝试触发Minor GC,如果之后空间仍不足,那么触发 Full GCstop the world的时间更长

相关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 前 MinorGCXX:+ScavengeBeforeFullGC

大对象处理策略

  • 遇到一个较大的对象时,就算新生代的伊甸园为空,也无法容纳该对象时,会将该对象直接晋升为老年代
/**
 *  演示内存的分配策略
 */
public class Main {
    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[_8MB]);
    }

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dnrfpB1f-1648738836383)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328220927476.png)]

线程内存溢出

  • 某个线程的内存溢出了而抛异常(out of memory),不会让其他的线程结束运行。这是因为当一个线程抛出OOM异常后它所占据的内存资源会全部被释放掉,从而不会影响其他线程的运行,进程依然正常
/**
 * 演示内存的分配策略
 */
public class Main {
    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 {
        new Thread(() -> {
            ArrayList<byte[]> list = new ArrayList<>();
            list.add(new byte[_8MB]);
            list.add(new byte[_8MB]);
        }).start();
		//主线程还是会正常执行
        System.out.println("sleep....");
        Thread.sleep(1000L);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OXEKc64t-1648738836383)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328221024193.png)]

垃圾回收器

相关概念

  • 并行收集:指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态
  • 并发收集:指用户线程与垃圾收集线程同时工作(不一定是并行的可能会交替执行)。用户程序在继续运行,而垃圾收集程序运行在另一个CPU上
  • 吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间 )),也就是。例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%

串行

  • 单线程
  • 堆内存较小,适合个人电脑
  • 开启串行回收器:XX:+UseSerialGC = Serial + SerialOld,新生代**-Serial**(使用复制) ,老年代-SerialOld(使用标记清除或者标记整理)
  • 安全点:让其他线程都在这个点停下来,以免垃圾回收时移动对象地址,使得其他线程找不到被移动的对象
  • 阻塞:因为是串行的,所以只有一个垃圾回收线程。且在该线程执行回收工作时,其他线程进入阻塞状态
  • Serial 收集器
    • 定义:Serial收集器是最基本的、发展历史最悠久的收集器
    • 特点:单线程收集器。采用复制算法。工作在新生代
  • Serial Old收集器
    • 定义:Serial Old是Serial收集器的老年代版本
    • 特点:单线程收集器。采用标记-整理算法。工作在老年代

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D22jro2N-1648738836384)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328222310518.png)]

吞吐量优先(建议再看视频)

  • 多线程

  • 堆内存较大,多核cpu

  • 让单位时间内,STW的时间最短(0.2 0.2=0.4 工作时间占比高)

  • JDK1.8默认使用的垃圾回收器

  • Parallel 收集器

    • 定义:与吞吐量关系密切,故也称为吞吐量优先收集器
    • 特点:并行的,工作于新生代,采用复制算法
  • Parallel Old 收集器

    • 定义:是Parallel 收集器的老年代版本
    • 特点:并行的,工作与老年代,采用标记-整理算法
  • 开启吞吐量优先回收器

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u1nNgV63-1648738836384)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328224734515.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MaJIdT7i-1648738836385)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328224758621.png)]

响应时间优先(建议再看视频)

  • 多线程
  • 堆内存较大,多核cpu
  • 尽可能让STW的单次的时间最短 (0.1 0.1 0.1 0.1 0.1=0.5 每次工作反应速度快)
  • 初始标记:标记GC Roots能直接到的对象。速度很快,存在Stop The World
  • 并发标记:进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行
  • 重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。存在Stop The World
  • 并发清理:对标记的对象进行清除回收
  • CMS收集器
    • 定义:Concurrent Mark Sweep(并发,标记,清除)
    • 特点:基于标记-清除算法的垃圾回收器。是并发的。工作在老年代
  • ParNew 收集器
    • 定义:ParNew收集器其实就是Serial收集器的多线程版本
    • 特点:工作在新生代,基于复制算法的垃圾回收器。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rjITLNN5-1648738836385)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328225050290.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6DEYQJz2-1648738836386)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220328225055809.png)]

G1

定义:Garbage First

  • JDK 9以后默认使用,而且替代了CMS 收集器

适用场景

  • 同时注重**吞吐量(*Throughput)和*低延迟(Low latency),默认的暂停目标是 200 ms
  • 超大堆内存,会将堆划分为多个大小相等Region
  • 整体上是 标记+整理 算法,两个区域之间是 复制 算法

相关参数:JDK8 并不是默认开启的,所需要参数开启

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zr7RwHsL-1648738836386)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220329214425726.png)]

G1垃圾回收阶段

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pNL5emQ1-1648738836386)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220329214621717.png)]

  • 新生代伊甸园垃圾回收—–>内存不足,新生代回收+并发标记—–>混合收集,回收新生代伊甸园、幸存区、老年代内存——>新生代伊甸园垃圾回收(重新开始循环)
Young Collection
  • 存在Stop The World
  • 分区算法region:分代是按对象的生命周期划分,分区则是将堆空间划分连续几个不同小区间,每一个小区间独立回收,可以控制一次回收多少个小区间,方便控制 GC 产生的停顿时间
  • E:伊甸园 S:幸存区 O:老年代

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0mLrJqVO-1648738836387)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220329215329163.png)]

Young Collection + CM
  • 在Young GC时会进行GC Root的初始标记
  • 老年代占用堆空间比例达到阈值时,进行并发标记(不会STW),有下面的JVM参数决定
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jt5RlZeE-1648738836387)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220329215715987.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-capvzKTk-1648738836388)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220329215725222.png)]

Mixed Collection
  • 会对E S O 进行全面的回收
  • 最终标记(Remark)会STW
  • 拷贝存活(Evacuation)会STW
  • -XX:MaxGCPauseMills:xxx :用于指定最长的停顿时间
  • :为什么有的老年代被拷贝了,有的没拷贝?
    • 因为指定了最大停顿时间,如果对所有老年代都进行回收,耗时可能过高。为了保证时间不超过设定的停顿时间,会回收最有价值(优先回收垃圾最多的区)的老年代(回收后,能够得到更多内存)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LYHZT8jk-1648738836388)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220329220337450.png)]

Full GC
  • SerialGC(串行)
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足发生的垃圾收集 - full gc
  • ParallelGC (并行
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足发生的垃圾收集 - full gc
  • CMS (并发)
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足
  • G1 (并发)
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足(老年代所占内存超过阈值,会触发触发并发收集和混合收集)
      • 如果垃圾产生速度慢于垃圾回收速度,不会触发Full GC,还是并发地进行清理
      • 如果垃圾产生速度快于垃圾回收速度,便会触发Full GC
Young Collection跨代引用
  • 新生代回收的跨代引用(老年代引用新生代)问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K04rOxPc-1648738836389)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220329221406327.png)]

  • 卡表:老年代被划为一个个卡表
  • Remembered Set :Remembered Set 存在于E(新生代)中,用于保存新生代对象对应的脏卡
  • 脏卡:O被划分为多个区域(一个区域512K),如果该区域引用了新生代对象,则该区域被称为脏卡
  • 在引用变更时通过 post-write barried + dirty card queue (异步操作,不会立刻完成脏卡的更新)
  • concurrent refinement threads 更新 Remembered Set

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-noTRVq4R-1648738836389)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220329221528963.png)]

Remark(重标记)

黑色:已被处理,需要保留的

灰色:正在处理中的

白色:还未处理的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mY447rok-1648738836390)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220329222253107.png)]

  • 并发标记过程中,有可能B被处理了以后未引用C,但该处理过程还未结束,在处理过程结束之前A引用了C,这时就会用到remark
    • 之前C未被引用,这时A引用了C,就会给C加一个写屏障,写屏障的指令会被执行,将C放入一个队列当中,并将C变为 处理中 状态
    • 并发标记阶段结束以后,重新标记阶段会STW,然后将放在该队列中的对象重新处理,发现有强引用引用它,就会处理它

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7cVMwKHw-1648738836391)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220329222745869.png)]

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内部,使用了不同的字符串表
JDK 8u40并发标记类卸载
  • 所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸载它所加载的所有类
  • -XX:+ClassUnloadingWithConcurrentMark 默认启用
JDK 8u60回收巨型对象
  • JDK 8u60 回收巨型对象一个对象大于 region 的一半时,称之为巨型对象
  • G1 不会对巨型对象进行拷贝
  • 回收时被优先考虑回收巨型对象
  • G1 会跟踪老年代所有 incoming 引用,这样老年代 incoming 引用为0(没有脏卡再引用它时)的巨型对象就可以在新生代垃圾回收时处理掉

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MUymd2rD-1648738836391)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220329224048370.png)]

JDK9并发标记起始时间的调整
  • 并发标记必须在堆空间占满前完成,否则退化为 FullGC
  • JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent (初始为45%)
  • JDK 9 可以动态调整
    • -XX:InitiatingHeapOccupancyPercent 用来设置初始值
    • 进行数据采样并动态调整
    • 总会添加一个安全的空档空间
JDK9 更高效的回收
  • 建议观看文档说明

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aPgMuXab-1648738836391)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220329224430695.png)]

垃圾回收调优

预备知识:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hPn6gB4l-1648738836392)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220329224948709.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NdLaYTRp-1648738836392)(C:\Users\maybe3032\AppData\Roaming\Typora\typora-user-images\image-20220329224819928.png)]

调优领域

  • 内存
  • 锁竞争
  • cpu占用
  • io

确定目标

  • 【低延迟】(互联网项目)还是【高吞吐量】(科学运算),选择合适的回收期
  • 低延迟优先:CMS(JDK9不推荐时间了),G1,ZGC
  • 高吞吐量:ParallelGC
  • Zing 虚拟机

最快的GC是不发生GC(减少了STW)

  • 查看Full GC前后的内存占用,考虑以下几个问题:
    • 数据是不是太多?
      • resultSet = statement.executeQuery("select * from 大表")
    • 数据表示是否太臃肿?
      • 对象图
      • 对象大小
    • 是否存在内存泄漏?
      • static Map map=
      • 第三方缓存实现

新生代调优

  • 新生代的特点
    • 所有的new操作分配内存都是非常廉价的
      • TLAB thread-local allocatior buffer(可以防止多个线程创建对象时的干扰)
    • 死亡对象回收代价为零
    • 大部分对象用过即死
    • Minor GC所用时间远小于Full GC
  • 新生代内存越大越好么?
    • 不是
      • 新生代内存太小:频繁触发Minor GC,会STW,会使得吞吐量下降
      • 新生代内存太大:老年代内存占比有所降低,会更频繁的触发Full GC。并且触发Minor GC时,清理新生代所花费的时间会更长
      • 最好在25%~50%
    • 新生代内存设置为能容纳 *[并发量 (请求-响应)]**的数据为宜
    • 幸存区大到能保留[当前活跃对象+需要晋升对象]
    • 晋升阈值配置得当,让长时间存活对象尽快晋升
      • -XX:MaxTenuringThreshold=threshold 调整最大晋升阈值
      • -XX:PrintTenuringDistribution

老年代调优

  • 以CMS为例:
    • CMS的老年代内存越大越好
    • 先尝试不做调优,如果没有Full GC,那么已经…,否则先尝试调优新生代
    • 观察发生Full GC时老年代内存占用,将老年代内存预设调大 1/4~1/3
      • -XX:CMSInitiatingOccupancyFraction=percent

案例

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

    • 原因:新生代内存紧张,导致了Minor GC频繁。然后新生代中的幸存区因为Minor GC频繁,所以导致幸存区的晋升阈值降低。很多生存周期短的对象晋升到了老年代中,最后也导致了Full GC频繁
    • 解决方案:先增大新生代的内存,内存增大后,Minor GC减少,同时增大幸存区的空间,增大了晋升的阈值,让生命周期较短的对象尽可能的留在新生代。让老年代的Full GC变得不频繁
  • 案例2:请求高峰期发生Full GC,单次暂停时间特别长(因为需要低延迟,所以使用CMS)

    • 原因:分析哪一部分耗时长,发现初始标记和并发标记都是很快的,重新标记比较慢(耗时长,会扫描整个堆内存,会根据对象找到引用)
    • -XX:+CMSScavengeBeforeRemark ,在重新标记之前,给新生代做垃圾回收,减少了新生代中存活的垃圾,最后可以减少在重新标记中的垃圾回收时间。
  • 案例3:老年代充裕情况下,发生Full GC(CMS jdk1.7)

    • 原因:JDK1.8是元空间,JDK1.7以前使用的是永久代。而永久代的内存不足,也会导致Full GC的出现,JDK1.8后是由操作空间来控制了,元空间的容量比较充裕
    • 增大元空间的初始值和默认值,保证Full GC不会发生
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值