JVM学习03:垃圾回收

news2024/9/24 20:28:05

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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/340655.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Security in Action 第三章 SpringSecurity管理用户

本专栏将从基础开始&#xff0c;循序渐进&#xff0c;以实战为线索&#xff0c;逐步深入SpringSecurity相关知识相关知识&#xff0c;打造完整的SpringSecurity学习步骤&#xff0c;提升工程化编码能力和思维能力&#xff0c;写出高质量代码。希望大家都能够从中有所收获&#…

牛客寒假集训营6 E 阿宁的生成树

E-阿宁的生成树_2023牛客寒假算法基础集训营6 (nowcoder.com)开始慢慢补牛牛的题题意&#xff1a;最小生成树质数距离思路&#xff1a;最小生成树一共就两种算法&#xff0c;我们考虑Prim的过程初始连通块是1&#xff0c;然后考虑拿1和其他的结点连边当j-i<k时边权是gcd&…

【JavaSE】方法的使用

方法的使用BIT-5-方法的使用绪论1. 方法概念及使用1.1什么是方法1.2 方法定义1.3 实参和形参的关系&#xff08;重要&#xff09;1.4 没有返回值的方法2. 方法重载2.1 为什么需要方法重载2.2 方法重载概念3. 递归3.1 生活中的故事3.2 递归的概念3.2 递归执行过程分析3.3 递归练…

C++之异常处理

异常异常是面向对象语言处理错误的一种方式。当一个函数出现自己无法处理的错误时&#xff0c;可以抛出异常&#xff0c;然后输的直接或者间接调用者处理这个错误。语法捕获全部的异常try {//可能抛出异常的代码//throw异常对象 } catch(...) {//不管什么异常&#xff0c;都在这…

Java之动态规划之子序列问题

目录 0.动态规划问题 一.最长递增子序列 1.题目描述 2.问题分析 3.代码实现 二.最长递增子序列 1.题目描述 2.问题分析 3.代码实现 三.最长重复子数组 1.题目描述 2.问题分析 3.代码实现 4.代码的优化(滚动数组) 四.最长公共子序列 1.题目描述 2.问题分析 3.代…

C语言学习笔记(五):数组的使用

数组的定义 数组是一组有序数据的集合。数组中各数据的排列是有一定规律的&#xff0c;下标代表数据在数组中的序号。 用数组名和下标即可唯一地确定数组中的元素。 数组中的每一个元素都属于同一个数据类型。 一维数组 定义与引用 int a[10] {0,1,2,3,4,5,6,7,8&#xf…

Matlab绘制隐函数总结-二维和三维

1.二维隐函数 二维隐函数满足f(x,y)0f(x,y)0f(x,y)0&#xff0c;这里无法得到yf(x)yf(x)yf(x)的形式。不能通过普通函数绘制。 我们要关注的是使用fplot函数和fimplicit函数。 第1种情况&#xff1a;基本隐函数 基本的隐函数形式形如&#xff1a; x2y22x2(x2y2)12x^{2}y^{…

【Qt】Qt 隐式共享

文章目录一、导读二、隐式共享简介三、源码角度分析隐式共享四、隐式共享在开发中的使用五、隐式共享迭代器问题六、隐式共享类和线程一、导读 在实际开发中&#xff0c;Qt中很多类可以直接作为函数参数传递&#xff0c;这样做是为了什么&#xff1f;其背后的实现机制又是什么…

进程,线程

进程是操作系统分配资源的基本单位&#xff0c;线程是CPU调度的基本单位。 PCB&#xff1a;进程控制块&#xff0c;操作系统描述程序的运行状态&#xff0c;通过结构体task,struct{…}&#xff0c;统称为PCB&#xff08;process control block&#xff09;。是进程管理和控制的…

#电子电气架构——Vector工具常见问题解决三板斧

我是穿拖鞋的汉子,魔都中一位坚持长期主义的工科男。 今天在与母亲聊天时,得到老家隔壁邻居一位大姐年初去世的消息,挺让自己感到伤感!岁月如流水,想抓都抓不住。想起平时自己加班的那个程度,可能后续也要自己注意身体啦。 老规矩,分享一段喜欢的文字,避免自己成为高知…

千锋教育嵌入式物联网教程之系统编程篇学习-03

目录 进程的终止 exit函数 _exit函数 进程退出清理 进程间的替换 进程间通信 常见通信机制 进程间通信的实质 信号 产生信号的方式 信号的默认处理方式 进程对信号的处理方式 kill函数 进程的终止 使用exit函数对进程进行终止&#xff0c;而return只是结束函数&a…

电子技术——共模抑制

电子技术——共模抑制 我们在之前学习过&#xff0c;无论是MOS还是BJT的差分输入对&#xff0c;共模信号并不会改变漏极电流的大小&#xff0c;因此我们说差分输入对共模信号无响应。但是实际上由于各种客观非理想因素&#xff0c;例如电流源有限阻抗等&#xff0c;此时共模是影…

LINUX提权入门手册

前言 发点存货 LINUX权限简介 在学习提权之前我们先了解一下linux里面的权限我们使用命令: ls -al即可查看列出文件所属的权限&#xff1a; 文件头前面都有一段类似的字符&#xff0c;下面我们仔细分析一下里面符号分别代表什么。 -rw-r--r-- 1 root root 第一个符号-的…

现代 cmake (cmake 3.x) 操作大全

cmake 是一个跨平台编译工具&#xff0c;它面向各种平台提供适配的编译系统配置文件&#xff0c;进而调用这些编译系统完成编译工作。cmake 进入3.x 版本&#xff0c;指令大量更新&#xff0c;一些老的指令开始被新的指令集替代&#xff0c;并加入了一些更加高效的指令/参数。本…

MongoDB--》MongoDB数据库以及可视化工具的安装与使用—保姆级教程

目录 数据库简介 MongoDB数据库的安装 MongoDB数据库的启动 MongoDB数据库环境变量的配置 MongoDB图形化管理工具 数据库简介 在使用MongoDB数据库之前&#xff0c;我们应该要知道我们使用它的原因&#xff1a; 在数据库当中&#xff0c;有常见的三高需求&#xff1a; Hi…

如何手写一个springboot starter?

本文主要分享了如何手写一个spring starter&#xff0c;把你的代码作为jar包进行开源。命名规则&#xff08;不要使用spring-boot开头&#xff0c;以避免将来spring-boot官方使用你的starter而重名&#xff09;官方命名格式为&#xff1a;spring-boot-starter-xxx非官方命名格式…

ucos-ii 的任务调度原理和实现

ucosii 任务调度和原理1、ucos-ii 任务创建与任务调度 1.1、任务的创建 当你调用 OSTaskCreate( ) 进行任务的创建的时候&#xff0c;会初始化任务的堆栈、保存cpu的寄存器、创建任务的控制块&#xff08;OS_TCB&#xff09;等的操作&#xff1b; if (OSTCBPrioTbl[prio] (OS_…

Python中如何书写文件路径

当程序运行时&#xff0c;变量是保存数据的好方法&#xff0c;但变量、序列以及对象中存储的数据是暂时的&#xff0c;程序结束后就会丢失&#xff0c;如果希望程序结束后数据仍然保持&#xff0c;就需要将数据保存到文件中。Python 提供了内置的文件对象&#xff0c;以及对文件…

数据库 delete 表数据后,磁盘空间为什么还是被一直占用?

插&#xff1a; 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 坚持不懈&#xff0c;越努力越幸运&#xff0c;大家一起学习鸭~~~ 最近有个上位机获取下位机上报数据的项目&#xff0c…

计算机网络6:Http协议

目录HTTP1.基本概念1.1.1 URI2. 请求和响应报文2.1.请求报文2.2.响应报文3.HTTP报文实现细节3.1响应头主要字段3.2HTTP状态码3.3 HTTP方法3.3.1 GET方法3.3.2 HEAD3.3.3 POST3.3.4 PUT3.3.5 PATCH3.3.6 DELETE3.3.7 OPTIONS3.3.8 CONNECT3.4 HTTP首部&#xff08;头部&#xff…