深入理解JVM-垃圾回收机制

深入理解JVM-垃圾回收机制

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

1.1 引用计数法

每个对象有一个引用计数器,当对象被引用一次则计数器加1,当对象引用失效一次则计数器减1,对于计数器为0的对象意味着是垃圾对象,可以被GC回收。

  • 引用计数法优点:实现逻辑简单
  • 引用计数法缺点:无法解决循环引用问题;目前没有在使用

image-20220805145907090

早期的Python虚拟机采用引用计数法,Java虚拟机采用可达性分析算法。

1.2 可达性分析算法

从GC Roots作为起点开始搜索,那么整个连通图中的对象便都是活对象,对于GC Roots无法到达的对象便成了垃圾回收的对象,随时可被GC回收。

  • Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
  • 扫描堆中的对象,看是否能够沿着 GC Root 对象 为起点的引用链找到该对象,找不到表示可以回收

哪些对象可以作为GC Root ?

  1. 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表
  2. 方法区中的类静态属性引用的对象。
  3. 方法区中常量引用的对象
  4. 本地方法栈中N( Native方法)引用的对象
1.2.1 演示GC Roots

MAT 内存分析工具

https://www.eclipse.org/mat/

image-20220805151547491

代码如下:

package cn.itcast.jvm.t2;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 演示GC Roots
 */
public class Demo2_2 {

    public static void main(String[] args) throws InterruptedException, IOException {
        List<Object> list1 = new ArrayList<>();
        list1.add("a");
        list1.add("b");
        System.out.println(1);
        System.in.read();

        list1 = null;
        System.out.println(2);
        System.in.read();
        System.out.println("end...");
    }
}

使用MAT需要抓取内存状态:

1、运行Demo2_2,在终端执行 jmap 命令,查看进程id

image-20220805151806558

2、执行命令:

jmap -dump:format=b,live,file=1.bin 21384

jmap -dump:format=b,live,file=2.bin 21384

format表示转储文件格式,Live表示只关注存活对象,live主动触发一次垃圾回收

image-20220805152049366

3、打开MAT,导入*.bin文件,找到GC Roots

image-20220805152142514

4、查看根对象

System Class;虚拟机运行过程中核心的类 java.lang.Class

Native Stack: Java虚拟机在执行操作系统系统调用时所用到的类

Busy Monitor: synchornized 被加锁的对象

Thread: 正在活动的线程

image-20220805152545758

ArrayList=null之前:

image-20220805152951555

之后,Arraylist被垃圾回收了:

image-20220805152843500

1.3 四种引用:强、软、弱、虚引用

https://blog.csdn.net/alpha_xia/article/details/122390225

image-20220805153144431

  1. 强引用
public class StrongReferenceUsage {

    @Test
    public void stringReference(){
        Object obj = new Object();
    }
}

上面我们new了一个Object对象,并将其赋值给obj,这个obj就是new Object()的强引用。

  • 只有所有GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收。
  1. 软引用(SoftReference)
  • 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象
  • 可以配合引用队列来释放软引用自身
  1. 弱引用(WeakReference)
  • 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
  • 可以配合引用队列来释放弱引用自身

当软引用和弱引用所引用的对象被回收时,会进入引用队列。

image-20220805153656534

  1. 虚引用(PhantomReference)
  • 必须配合引用队列使用,主要配合ByteBuffer使用,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法释放直接内存
  1. 终结器引用(FinalReference)
  • 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由Finalizer线程通过终结器引用找到被引用对象并调用它的 finalize方法,第二次GC 时才能回收被引用对象

1.4 软引用的应用

当我们读取图片的时候,会把图片存储到ArrayList集合中,当图片过多时,会导致内存溢出的错误。采用软引用,将暂时不需要的图片先释放,之后需要的时候再次读取。强引用就无法实现。

package cn.itcast.jvm.t2;

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

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

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



    public static void main(String[] args) throws IOException {
        /*List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(new byte[_4MB]);
        }

        System.in.read();*/
        soft();


    }

    public static void soft() {
        // list --> SoftReference --> byte[]

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

image-20220805155119267

加上-XX:+PrintGCDetails 打印垃圾回收过程

image-20220805155753662

关联引用队列后,完成对软引用的回收。

package cn.itcast.jvm.t2;

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

    }
}

可以发现,四个null值被移除出去了。

image-20220805160218677

1.5 弱引用的应用

package cn.itcast.jvm.t2;

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

image-20220805160521886

2、垃圾回收算法

2.1 标记清除

**定义:**Mark Sweep

描述:分为标记清除两阶段:首先标记出所有需要回收的对象,然后统一回收所有被标记的对象。

  • 速度较快
  • 会造成内存碎片,导致在程序运行过程中需要分配较大对象的时候,无法找到足够的连续内存而不得不提前触发一次垃圾收集动作。

image-20220806161009047

清除只需要把不需要内存的起始地址和大小存入列表中即可,并没有刻意地去清零。

2.2 标记整理

**定义:**Mark Compact

**描述:**标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

  • 速度慢
  • 没有内存碎片

image-20220806161251249

2.3 复制

**定义:**Copy

**描述:**将可用内存容量划分为大小相等的两块,每次只用其中一块。当这块内存用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

  • 不会有内存碎片,但效率也不是很高
  • 需要占用双倍内存空间

**优点:**自带整理功能,这样不会产生大量不连续的内存空间,适合年轻代垃圾回收。

image-20220806161544786

3、分代垃圾回收

3.1 垃圾分代和回收

当前商业虚拟机的垃圾收集都采用分代收集。此算法没啥新鲜的,就是将上述三种算法整合了一下。具体如下:

根据各个年代的特点采取最适当的收集算法:

  1. 在新生代中,每次垃圾收集时候都发现有大批对象死去,只有少量存活,那就选用复制算法。只需要付出少量存活对象的复制成本就可以完成收集。
  2. 老年代中因为对象存活率高、没有额外空间对他进行分配担保,就必须用标记-清除或者标记-整理。

image-20220806162258181

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

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

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

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

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

3.2 相关JVM参数

相关JVM参数

image-20220806163353857

3.3 GC 分析

-Xms20M -Xmx20M 初始和最大堆空间都是20M

-Xmn10M 新生代10M

-XX:+UseSerialGC 垃圾回收器

-XX:+PrintGCDetails -verbose:gc -XX:-ScavengeBeforeFullGC 打印GC详情

package cn.itcast.jvm.t2;

import java.util.ArrayList;

/**
 *  演示内存的分配策略
 */
public class Demo2_1 {
    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);
    }
}

没有运行任何代码的堆空间的初始化

image-20220806164348217

image-20220806165139629

image-20220806165624877

image-20220806165827930

一个线程内的outofmemory不会导致主进程结束。

4、垃圾回收器

4.1 串行

Serial(串行)垃圾收集器是最基本、发展历史最悠久的收集器;JDK1.3.1前是HotSpot新生代收集的唯一选择;

  • 单线程
  • 针对新生代
  • 采用复制算法
  • 进行垃圾收集时,必须暂停所有工作线程,直到完成
  • 堆内存较小,适合个人电脑

-XX:+UseSerialGC = Serial + SerialOld // -XX:+UseSerialGC 添加该参数来显示的使用串行垃圾收集器

Serial 复制算法(新生代)

SerialOld 标记算法(老年代)

image-20220808142242995

4.2 吞吐量优先

  • 多线程
  • 堆内存较大,多核CPU
  • 让单位时间内,STW的时间最短 0.2 0.2 = 0.4

-XX:+UseParallelGC ~ -XX:+UseParallelOldGC // JDK1.8默认开启,只要开启UseParallelGC,就对应开启
-XX:+UseAdaptiveSizePolicy // 自适应动态调整伊甸园和幸存区的内存比例
-XX:GCTimeRatio=ratio // 目标1:1 / (1 + ratio) 一般设置ratio为19,20分钟垃圾回收不超过1分钟;会动态调整堆空间大小适应
-XX:MaxGCPauseMillis=ms // 目标2:最大暂停用户线程时间,默认200ms
-XX:ParallelGCThreads=n // 垃圾回收线程数
// 垃圾回收时,CPU会飚得很高

image-20220808142735309

4.3 响应时间优先

  • 多线程
  • 堆内存较大,多核CPU
  • 尽可能让单次STW的时间最短 0.1 0.1 0.1 0.1 0.1 = 0.5

-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld
-XX:ParallelGCThreads=n ~ -XX:ConcGCThread=threads // ParallelGCThreads为4(默认的CPU核数) ,则ConcGCThread应该是ParallelGCThreads的1/4,对CPU占用没有Par那么高
-XX:CMSInitiatingOccupancyFraction=percent // 执行CMS执行占比,预留空间给浮动垃圾
-XX:+CMSScavengeBeforeRemark // 在CMS垃圾标记前开启新生代垃圾回收,这样重新标记对象要少得多,Full GC时间从接近2秒,降低到300ms左右
//CMS致命问题:CMS会产生内存碎片,如果内存碎片过多,垃圾回收会退化到SerialOld单线程垃圾回收器

(+UseConcMarkSweepGC user concurrent mark sweep (并发、标记、清除))

初始标记非常快,不影响用户工作工程;并发标记

image-20220808150100026

  • 初始标记:仅仅单线程标记GC Roots的直接关联对象,并且STW,这个过程非常短暂,可以忽略不计;

  • 并发标记:使用GC Roots Tracing算法,进行跟踪标记RC Roots间接相关的对象,不会STW;

  • 重新标记:因为之前并发标记,其他用户线程不暂停,可能产生了新垃圾,所以需要重新标记;

  • 清除垃圾:与用户线程并行执行垃圾回收,使用清除算法

CMS缺点:因为与用户工作程一起并发执行,所以会边清理,一边会产生新的垃圾

4.4 G1 垃圾回收器

**定义:**Garbage First,优先回收最有价值的垃圾区域,达到暂停时间不短的目标

  • 2004 论文发布
  • 2009JDK 6u14体验
  • 2012 JDK 7u4官方支持
  • 2017 JDK 9默认,同时废弃了CMS垃圾回收

适用场景

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

相关JVM参数

  • -XX:+UseG1GC
  • -XX:G1HeapRegionSize=size // 设置Region区域大小
  • -XX:MaxGCPauseMillis=time // 设置暂停目标,默认是200ms

总结:G1垃圾回收器,使用标记-整理算法,可以避免CMS标记-清除算法产生的内存碎片问题;在两个Region区域之间,则是使用复制算法。JDK8没有默认G1垃圾回收器,需要手动开启G1

1)G1垃圾回收阶段

image-20220825091719649

  • Young Collection
  • Young Collection + Concurrent Mark
  • Mixed Collection
2) Young Collection 新生代回收

会STW

image-20220825091902720

如果伊甸园进行垃圾回收,则会将伊甸园区存活的对象使用复制算法到Survivor区

image-20220825091910900

当Survivor进行垃圾回收时,对象年龄超过15次,放入老年代;年龄不足15次放入另一个Survivor区域

image-20220825091928438

3) Young Collection + CM(新生代回收+CM)
  • 在Young GC 时会 GC Root 的初始标记
  • 老年代占用堆空间比例达到阈值时,进行并发标记(不会STW),由下面的JVM参数决定

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

image-20220825092638623

4)Mixed Collection (混合回收)

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

  • 最终标记(Remark)会STW
  • 拷贝存活(Evacuation)会STW ,并不是所有老年代区域都会回收,而是回收最有价值

-XX:MaxGCPauseMillis=ms

image-20220825092742492

5)Full GC

Serial GC

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

Parallel GC

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

CMS

  • 新生代内存不足发生的垃圾收集 - minor gc
  • 老年代内存不足,当回收速度高于垃圾产生的速度,后台不会有full gc字样

G1

  • 新生代内存不足发生的垃圾收集 - minor gc
  • 老年代内存不足,当回收速度高于垃圾产生的速度,后台不会有full gc字样
6)Young Collection 跨代引用

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

如果遍历整个老年代根对象,显然效率会非常低;老年代设计对应一个卡表,每个卡512K,如果某个卡中的对象引用了对象,我们将此卡标记为脏卡,减少扫描范围,提升垃圾回收效率。

image-20220825093845474

  • 卡表与Remembered Set
  • 在引用变更时通过 post-write barrier + dirty card queue
  • concurrent refinement threads 更新Remembered Set

image-20220825094129129

7)Remark 重标记

pre - write barrier + satb_mark_queue

在对象引用改变之前,采用写屏障,表示未处理完毕;同时将对象存入一个引用队列进行处理

image-20220825094204284

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 内部,使用了不同的字符串表
9)JDK 8u40 并发标记类卸载

所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸载它所加载的所有类

-XX:+ClassUnloadingWithConcurrentMark 默认启用

10)JDK 8u60回收巨型对象
  • 一个对象大于region的一半时,称之为巨型对象
  • G1 不会对巨型对象进行拷贝
  • 回收时被优先考虑
  • G1 会跟踪老年代所有 incoming 引用,这样老年代incoming 引用为0的巨型对象就可以在新生代垃圾回收时处理掉

如下图,巨型对象在G1垃圾回收模型情况:

image-20220825101056150

11)JDK 9 并发标记起始时间的调整

并发标记必须在堆空间占满前完成,否则退化为Full GC (如果垃圾回收回收速度跟不上垃圾产生的速度,最终会Full GC)

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

JDK9 可以动态调整,更加合理;尽可能避免并发垃圾回收退化Full GC垃圾回收

  • -XX:InitiatingHeapOccupancyPercent 用来设置初始值
  • 进行数据采样并动态调整
  • 总会添加一个安全的空档空间
12)JDK9 更高效的回收
  • 250+ 增强
  • 180+bug 修复
  • https://docs.oracle.com/en/java/javase/12/gctuning
  • G1 更加成熟、更加稳定

5、垃圾回收调优

预备知识

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

  • 主要参考官网、更权威https://docs.oracle.com/en/java/javase/11/tools/java.html
  • 查看虚拟机运行参数:java -XX:+PrintFlagsFinal -version | findstr “GC”

掌握相关工具

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

调优原则:让长时间存活对象尽快晋升,如果长时间存活对象大量停留在新生代,新生代采用复制算法,复制来复制去,性能较低而且是个负担

5.1 调优领域

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

5.2 确定目标

  • 【低延迟】还是【高吞吐量】,选择合适的回收器
  • CMS,G1, ZGC (低延迟)
  • ParallelGC (高吞吐量)
  • Zing

科学运算,追求高吞吐量;互联网项目追求低延迟;高吞吐量垃圾回收,目前没有太多选择就一下ParallelGC;

低延迟垃圾回收,可以选CMS,G1, ZGC。目前互联公司还是很多在用CMS,JDK9 默认G1,不推荐CMS;因为CMS采用标记-清除算法会产生内存碎片,内存碎片多了之后会退化为serialOld,产生大幅度、长时间停顿,给用户的体验是不稳定

5.3 最快的GC是不生发GC

  • 查看Full GC 前后内存占用,考虑下面几个问题:自己的代码是否存在问题
    • 数据是不是太多?
      • resultSet = statement.executeQuery(“select * from 大表”) ,可以加限定条数 limit n
    • 数据表示是否太臃肿
      • 对象图
      • java对象最小也是16字节,Integer 16字节, int 4;所以我们在选则数据类型时尽量选用基本数据类型
    • 是否存在内存泄漏?
      • 比如定义了一个静态的Map,static Map map = ,然后不停地向里面添加数据
      • 在内存紧张时,可以使用软引用
      • 在内存不足时,可以使用弱引用
      • 缓存数据时,尽量使用第三方缓存实现,比如redis/memcache,减少对堆内存依赖

5.4 新生代调优

新生代的特点

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

    • TLAB thread-local allocation buffer,线程局部缓冲区,线程使用自己私有区域分配对象内存(不会对其他线程的内存产生干扰,并发问题)
  • 死亡对象的回收代价是零;因为采用复制算法,存活的对象使用复制算法到Survivor区域,剩下都是需要被回收的

  • 大部分对象用过即死,只有少数对象存活

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

  • 新生代优化空间更大一些

如何给新生代调优呢?是不是将新生代内存调得越大越好?下面是Oracle官方文档说明截图

网页链接:https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE

image-20220826095011596

上述大致中文翻译:设置年轻代的堆的初始大小和最大大小(以字节为单位)。 字母k或K表示千字节,m或M表示兆字节,g或G表示千兆字节。 堆的年轻代区域用于新对象。 与其他区域相比,在该区域执行GC的频率更高。 如果年轻代设置太小,则会执行大量 minor gc垃圾回收。 如果设置太大,则仅执行full gc垃圾回收才有效,这可能需要很长时间才能完成。 Oracle官方建议设置年轻代的大小保持大于堆总大小的25%,并且小于堆总大小的50%。

总结:新生代,还是需要调大一些,因为新生代采用复制算法,需要移动对象,复制算法性能效率较低。

公式:新生代能容纳所有【并发量 * (请求 - 响应)】的数据

  • 幸存区大到能保留【当前活跃对象 + 需要晋升对象】,原则就是让真正需要进入老年代的对象才进入老年代。

  • 晋升阈值配置得当,让长时间存活对象尽快晋升

    -XX:MaxTenuringThreshold=threshold // 设置年龄阈值,大值为15。并行(吞吐量)收集器的默认值为15,而CMS收集器的默认值为6。

    -XX:+PrintTenuringDistribution // 启用打印保有权年龄信息,这个参数对于设置-XX:MaxTenuringThreshold有很大帮助,阀值需要长时间观察对象分布,设置合理即可。

5.5 老年代调优

以CMS 为例

  • CMS的老年代内存越大越好
  • 先尝试不做调优,如果没有Full GC 那么老年代已经足够大了;如果有Full GC 则先尝试调优新生代
  • 观察发生Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3 \
    • -XX:CMSInitiatingOccupancyFraction=percent // 控制老年代占用空间大小占总空间大小比例,进行CMS垃圾回收;值越小就越早进行垃圾回收,推特工程师有一个演讲建议将此值设置为0,即一有垃圾就回收;一般我们将此值设置75%~80%之间,预留25%-20%给浮动垃圾

5.6 案例

案例1:Full GC 和Minor GC频繁(一分钟上百次),意味着堆内存空间紧张,可能是新生代空间过小,导致不需要晋升到老年代的对象进入老年代,然后老年代空间存在大量这种对象,空间也紧张就是频繁gc;

解决方法:增加新生代内存

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

解决方法:可以重新标记前开启垃圾回收,这样重新标记对象数没有那多,性能有一定提高;

案例3:老年代充裕情况下,发生Full GC (1.7) ;可能是JDK1.7永久代空间不足导致内存不足;JDK1.8元空间使用系统内存不易内存溢出

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值