admin管理员组

文章数量:1530309

相关文章:

  • JVM中的新生代和老年代(Eden空间、两个Survior空间)_jvm eden_样young的博客-CSDN博客
  • JAVA命令行工具(一)--JAVA - 简书
  • JAVA命令行工具(二)-jps - 简书
  • JAVA命令行工具(三)-jstat - 简书
  • 记CPU占用过高的排查 - 简书
  • jstat命令详解_zhaozheng7758的博客-CSDN博客
  • 深入浅出JVM调优,看完你就懂_Lemon-的博客-CSDN博客
  • JVM性能调优_jvm调优_滴哩哩哩滴哩哩哩哒哒的博客-CSDN博客
  • 性能调优-------(三)1分钟带你入门JVM性能调优_小诚信驿站的博客-CSDN博客
  • JVM 面试点: 新生代的内存大小参数设置_jvm设置新生代大小_困知勉行1985的博客-CSDN博客
  • JVM中FGC和YGC分析_jvm fgc_水月清辉的博客-CSDN博客
  • Java GC 变量含义(S0 S1 E O P YGC YGCT FGC FGCT GCT)_fgc ygc_厚积_薄发的博客-CSDN博客
  • jvm优化技巧,Java堆,old区,Eden区,s0和s1区,老年代,新生代_jvm s0 s1_互联网全栈开发实战的博客-CSDN博客
  • jvm优化技巧,Java堆,old区,Eden区,s0和s1区,老年代,新生代_jvm s0 s1_互联网全栈开发实战的博客-CSDN博客
  • JVM参数调优-设置堆、新生代、老年代、持久代大小_jvm调整老年代,新生代的大小_坚持是一种修行的博客-CSDN博客
  • JVM 新生代和老年代介绍_jvm新生代老年代_Codetots的博客-CSDN博客

虚拟机栈:存储基本数据类型、引用对象的变量、局部变量表等,这是线程私有的,每个线上线程的大小默认为1Mb。

程序计数器:存储字节指令的地地址,如果是本地方法栈,则存储undefined。

本地方法栈:由于java是表层语言,无法直接访问硬件,需要调用第三方语言,比如C、C++来操作硬件,比如创建内核线程,操作文件等。

方法区:存储jvm编译后地字节码文件,静态变量,常量,类信息等。

堆:

  • 这是一块很重要的存储区域,也是我们性能调优重要依据,其用来存储java对象,gc回收的也是这一块数据。其分为老年代和新生代,默认数据大小为2 :1。
  • 新生代又分为Eden区,s0区,s1区,默认数据大小为8 : 1 : 1。
  • 新创建一个对象,首先判断能否放到Eden区,如果Eden区满了,会触发mirrorGc「所有的 Minor GC 都会触发“全世界的暂停(stop-the-world)”,停止应用程序的线程,但这段时间可以忽略不计」。此时Eden区和s0区中存活的对象移至s1区,并标志对象的分代年龄,eden区和s0区清空,如果此时对象还无法放置eden区,则直接放置老年代。反之亦然。
  • 分代年龄存储到java对象头中。如果old区满了,会触发fullGc,我们尽量避免fullGc「fullGc暂停所有正在执行的线程(Stop The World),来回收内存空间,这个时间需要考虑地」。

因而,我们所说的性能调优,那么就是堆中的性能调优。
 

笔记参考文章:

JVM学习笔记——垃圾回收篇-腾讯云开发者社区-腾讯云

JVM 学习笔记(二)垃圾回收_CodeAli的博客-CSDN博客

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

1. 引用计数法

当一个对象被引用时,就当引用对象的值加一,当值为 0 时,就表示该对象不被引用,可以被垃圾收集器回收。这个引用计数法听起来不错,但是有一个弊端,如下图所示,循环引用时,两个对象的计数都为1,导致两个对象都无法被释放。(java虚拟机垃圾回收没有采用它)

 2. 可达性分析算法

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

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

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

堆分析工具:MAT,eclipse官网下载地址:Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation

 2.2 案例

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

        ArrayList<Object> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add(1);
        System.out.println(1);
        System.in.read();

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

第一步:运行上面的代码后,找到.class文件的位置,选中->右键->Open In->Terminal,然后使用命令jps查看当前系统中有哪些 java 进程;

第二步:执行如下命令,生成转储文件1.bin,然后在Run窗口回车让程序继续执行下面的代码,最后再回到Terminal窗口执行命令 jmap -dump:format=b,live,file=2.bin 10396 这时便生成了两个转储文件;

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

参数解释:

  • 参数"-dump:":表示不是查看堆内存的使用情况,而是要把当前堆内存的状态转储到文件;
  • format=b:表示指定转储文件的格式为二进制格式;
  • live:抓快照时只关心存活的对象,过滤掉那些被垃圾回收掉的;且这个参数表示抓快照前会进行一次垃圾回收,因为进行垃圾回收后才知道哪些是存活对象;
  • file=1.bin:指定转储文件的文件名;(当前目录下)
  • 10396:进程id

第三步:用刚刚下载的堆内存分析工具MAT打开上面生成的文件进行分析;对比两个文件发现,ArrayList对象被回收掉了。

延伸:区分引用变量和对象

MAT工具使用遇到的问题

双击运行程序时,提示下面的错误。此时,需要在官网下载一个JDK11的压缩包,解压后放在磁盘中某个位置,如D:\JavaTools\jdk-11.0.19;然后打开MAT安装目录下的MemoryAnalyzer.ini文件,添加下方两行代码,表示指定以JDK11方式启动。这样,再次双击运行MAT工具时,便可正常启动。

-vm
D:/JavaTools/jdk-11.0.19/bin/javaw.exe

MAT工具使用的文章

  • Java内存分析工具MAT(Memory Analyzer Tool)的介绍与使用_刘Java的博客-CSDN博客
  • MAT(Memory Analyzer Tool)工具使用超详细版_mat工具_隐0士的博客-CSDN博客
  • MemoryAnalyzer指定JDK版本_memoryanalyzer 指定jdk_大数据男的博客-CSDN博客

3. 四种引用

强引用

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

软引用(SoftReference)

  • 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象;
  • 可以配合引用队列来释放软引用自身。

弱引用(WeakReference)

  • 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象;
  • 可以配合引用队列来释放弱引用自身。

虚引用(PhantomReference)

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

终结器引用(FinalReference)

  • 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize方法,第二次 GC 时才能回收被引用对象。

3.1 演示软引用

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

    public static int _4MB = 4 * 1024 * 1024;

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

    // 设置 -Xmx20m , 演示堆内存不足,
    public static void method1() throws IOException {
        ArrayList<byte[]> list = new ArrayList<>();

        for(int i = 0; i < 5; i++) {
            list.add(new byte[_4MB]);
        }
        System.in.read();
    }

    // 演示 软引用
    public static void method2() throws IOException {
        ArrayList<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());
        }
    }
}

method1 方法解析:
首先会设置一个堆内存的大小为 20m,然后运行 mehtod1 方法,会抛异常,堆内存不足,因为 mehtod1 中的 list 都是强引用。 

method2 方法解析:
在 list 集合中存放了 软引用对象,当内存不足时,会触发 full gc,将软引用的对象回收。细节如图:

上面的代码中,当软引用引用的对象被回收了,但是软引用还存在,所以,一般软引用需要搭配一个引用队列一起使用。
修改 method2 如下: 

    // 演示 软引用 搭配引用队列
    public static void method3() throws IOException {
        ArrayList<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[]> ref : list) {
            System.out.println(ref.get());
        }
    }

3.2 弱引用演示

public class Code_09_WeakReferenceTest {

    public static void main(String[] args) {
//        method1();
        method2();
    }

    public static int _4MB = 4 * 1024 *1024;

    // 演示 弱引用
    public static void method1() {
        List<WeakReference<byte[]>> list = new ArrayList<>();
        for(int i = 0; i < 10; i++) {
            WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB]);
            list.add(weakReference);

            for(WeakReference<byte[]> wake : list) {
                System.out.print(wake.get() + ",");
            }
            System.out.println();
        }
    }

    // 演示 弱引用搭配 引用队列
    public static void method2() {
        List<WeakReference<byte[]>> list = new ArrayList<>();
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

        for(int i = 0; i < 9; i++) {
            WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB], queue);
            list.add(weakReference);
            for(WeakReference<byte[]> wake : list) {
                System.out.print(wake.get() + ",");
            }
            System.out.println();
        }
        System.out.println("===========================================");
        Reference<? extends byte[]> poll = queue.poll();
        while (poll != null) {
            list.remove(poll);
            poll = queue.poll();
        }
        for(WeakReference<byte[]> wake : list) {
            System.out.print(wake.get() + ",");
        }
    }

}

首先学习老师视频52~56,若想进一步学习java的四种引用,可参考以下资料:

  • 大厂面试知识之:Java的四大引用 - 知乎
  • JVM 系列(5)吊打面试官:说一下 Java 的四种引用类型 - 知乎
  • 详解 Java 中的四种引用___Hiro__的博客-CSDN博客
  • 四种引用类型在JAVA Springboot中的使用详解_java_脚本之家
  • 面试官:详细谈谈Java对象的4种引用方式_java_脚本之家
  • Java的四种引用方式_java_脚本之家

二、垃圾回收算法

1. 标记清除

        定义: Mark Sweep

        特点:①速度较快;②会造成内存碎片

2. 标记整理

        定义:Mark Compact

        特点:①速度慢;②没有内存碎片

3. 复制

        定义:Copy
        特点:①不会有内存碎片;②需要占用双倍内存空间

 
三、分代垃圾回收

  • 对象首先分配在伊甸园区域
  • 新生代空间不足时,触发 minor gc,伊甸园和 from 存活的对象使用 copy 复制到 to 中,存活的对象年龄加 1并且交换 from to
  • minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
  • 当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)
  • 当老年代空间不足,会先尝试触发 minor gc,如果之后空间仍不足,那么触发 full gc,STW的时间更长 

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

更多参数的理解,请移步: JAVA命令行工具(一)--JAVA垃圾回收选项 - 简书

3.2 GC分析

(1)main中无任何代码时

问题一:上图中,新生代总大小total为什么只有9M多?

答:JVM参数新生代大小参数【-Xmn10M】明明划分了10M,可是为什么只有9M多呢?因为幸存区比例参数【-XX:SurvivorRatio=ratio】添加后,8M会分给伊甸园,幸存区from和幸存区to各占1M。这里就认为幸存区to那一兆的空间要始终空着,是不能用的,所以计算空间时,那1M就没有计算在内。

扩展:-XX:SurvivorRatio=ratio参数详解

  设置Survivor和Eden的相对大小,该选项作用于新生代内部。为了明白该选项的含义,需要了解新生代的空间划分,一个简单的示意如下:

    |  Eden  | From(S0) | To(S1) | // Trom和To都是Survivor且大小相同

该选项表示Eden区和两个Surivior区的比率,默认值为8,即各区的比例为Eden:S0:S1=8:1:1。如果Eden区过大,在一次GC后幸存的对象过多以至于Survivor区不能容纳,那么大部分对象在一次GC后就会转移到老年代;如果Eden区过小,意味着新对象的产生将很快耗尽Eden区,从而增加新生代GC次数。

问题二:上面代码中只有main方法,内部什么都没有,为什么eden伊甸园空间已经使用了28%?

因为哪怕是再简单的一个java程序运行时,都要加载一些类,创建一些对象;这些对象刚开始也是使用的伊甸园区域。

(2)main中创建list集合并分配7M

 注意:如何区分老年代GC还是新生代GC呢?

  • 上图中如果是GC开头时,表示新生代GC;如果为full GC时,表示老年代GC;

(3)在(2)基础上再分配512K

(4)在(3)基础上再分配512K

(5)大对象直接晋升老年代

(6)再加8M出现OutOfMemory

(7)一个线程内的OutOfMemory不会导致整个java进程的结束

四、垃圾回收器分类

4.1 串行

  • 特点:单线程
  • 适用场景:堆内存较小,适合个人电脑

-XX:+UseSerialGC = Serial + SerialOld

4.2 吞吐量优先

  • 多线程
  • 堆内存较大,多核 cpu
  • 让单位时间内,STW 的时间最短 0.2 0.2 = 0.4,垃圾回收时间占比最低,这样就称吞吐量高

STW:stop the World 

-XX:+UseParallelGC ~ -XX:+UseParallelOldGC
-XX:+UseAdaptiveSizePolicy
-XX:GCTimeRatio=ratio
-XX:MaxGCPauseMillis=ms
-XX:ParallelGCThreads=n

 

 -XX:GCTimeRatio=ratio
-XX:MaxGCPauseMillis=ms   最大暂停毫秒数,默认值200ms

        这两个参数都是目标参数,但这两个目标其实是冲突的。因为如果调整了TimeRatio,一般是要把堆调大(即默认是0.01不容易达到,要将分母ratio调小,如调为19使结果变为0.05,工作100分钟,5分钟用于垃圾回收一般都能达到),堆调大后吞吐量就提升了,但是堆大了每一次垃圾回收的时间就会增长,那就没办法达到MaxGCPauseMillis为200ms这个指标了。

        反之,如果让MaxGCPauseMillis这个时间变的短,意味着堆空间应该小一些,这样垃圾回收时间才会短,而这样的话,吞吐量又降下去了。所以,这两个参数是对立的目标,实际要取折中值。

        ratio一般设置值为19

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:ConcGCThreads=threads
-XX:CMSInitiatingOccupancyFraction=percent
-XX:+CMSScavengeBeforeRemark

注意:

  • 吞吐量优先中参数UseParallelGC是Parallel,"并行"的意思,指多个垃圾回收器并发运行,但在此期间,不允许用户线程运行了;
  • 响应时间优先参数UseConcMarkSweepGC是Concurrent,"并发"的意思,垃圾回收器在工作的同时,用户线程也能同时进行,两者并发执行,都要去抢CPU;

 4.4 G1垃圾回收器

定义

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

适用场景

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

相关 JVM 参数

  • -XX:+UseG1GC    开启Garbage One即G1垃圾回收器,JDK1.8需要开启,1.9开始默认。
  • -XX:G1HeapRegionSize=size
  • -XX:MaxGCPauseMillis=time

垃圾回收的三个阶段:(三个阶段循环进行)

Young GC:会STW,但时间很短;

Young GC + 并发标记:

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

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

Mixed GC:

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

  • 最终标记(Remark)会STW
  • 拷贝存活(Evacuation)会STW

-XX:MaxGCPauseMillis=ms

为什么图中并不是所有的Old区都进行复制呢?
        G1垃圾回收器会根据设定的最大暂停时间(-XX:MaxGCPauseMillis=ms)有选择地进行垃圾回收。怎么理解呢?因为有时候堆内存的空间太大了,老年代的垃圾回收时间比较长,达不到设定的MaxGCPauseMillis目标,于是为了达到此目标,G1会从所有的老年代区域挑出那些回收价值最高的区域进行复制(老年代的回收算法是复制算法),这样自然垃圾回收时间就变短了。这也就是为什么官方叫Garbage First的原因,因为它优先回收垃圾最多的Region。

Full GC概念辨析

SerialGC(串行)

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

ParallelGC(并

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

CMS(并发垃圾回收器)

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

G1(并发垃圾回收器)

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

四款垃圾回收器,新生代内存不足时都叫minor gc;但老年代却不同,SerialGC 和 ParallelGC老年代内存不足发生的垃圾收集都叫full gc,而CMS和G1垃圾回收器老年代内存不足时分两种情况,下面以G1为例进行说明:当老年代的内存达到默认阈值45%时,会触发并发标记、混合回收这两个阶段:

  • 如果回收速度高于新的用户线程产生垃圾速度时,这个时候不叫full gc,这个时候还是并发垃圾回收的阶段,虽然也有重新标记和数据拷贝的过程,也有暂停,但这个暂停时间还是很短的,称不上full gc;
  • 如果回收速度<新产生垃圾速度时,这个时间并发收集就失败了(和CMS类似),才会full gc。可以看一下日志,只有打印出full gc字样时,才是full gc。

Young Collection 跨代引用

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

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

在新生代垃圾回收时,首先也是先找到根对象,进行可达性分析找到存活对象,存活对象复制到幸存区。这里就有个问题,根对象有一部分来自于老年代,老年代的存活对象非常多,如果遍历整个老年代,就非常耗时,于是就采用一种卡表(Card Table)的技术,将老年代的区域再进行细分,分成一个个的card,每个card大概是512K。如果老年代有对象引用了新生代的一个对象,就把对应的卡标记为脏卡,这样的好处就是在找根对象的时候,只需要关注那些dirty card脏卡就可以了。其实就是减少搜索范围,提高寻找根对象的效率。新生代这边有个Remembered Set,用于记录外部对它的一些引用,即记录有哪些dirty card。

在引用变更时通过 post-write barrier写屏障去标记脏卡,异步标记,会把更新的指令放在一个脏卡队列中,将来一个线程去更新。

Remark重新标记阶段

pre-write barrier  + satb_mark_queue

  • 黑色:已经处理完成的,会被保留下来;
  • 灰色:中间状态,如果在并发标记阶段被标记为垃圾(白色)后,又有用户线程对它进行了引用,引用地址发生改变后,写屏障会将其加入一个satb_mark_queue队列,改为灰色,等待重新标记阶段进行再次处理;
  • 白色:没有引用的对象,即会被回收的对象。

最后,建议阅读官方文档调优指南:Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide, Release 12

五、垃圾回收调优

查看默认GC参数的命令(Windows):

java -XX:+PrintFlagsFinal -version |findstr "GC"

在官网学习命令:java

5.3 最快的GC是不发生GC 

5.4 新生代调优

排除了自己代码的问题之后,再进行内存调优。内存调优建议从新生代开始,因为新生代的优化空间更大点。

5.4.1 新生代的特点

  • 所有的 new 操作的内存分配非常廉价(TLAB thread-local allocation buffer)
  • 死亡对象的回收代价是零
  • 大部分对象用过即死
  • Minor GC 的时间远远低于 Full GC

有些人可能会说,新生代调优不就是把新生代的内存调大嘛,当然这是最有效的方式。但调大新生代也会存在一些问题。

5.4.2 新生代调优

5.5 老年代调优

以CMS为例进行说明。

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

        CMS垃圾回收器特点:低响应时间;并发收集。缺点:垃圾回收器在工作的同时,用户线程也在工作,这样就会产生新的垃圾(这些垃圾称为浮动垃圾)。如果浮动垃圾产生又导致内存不足,这时候就事大了,就会导致CMS并发失败,这时CMS垃圾回收器就不能正常工作了,就会退化为Serial Old,工作效率就会变低。所以,在给老年代规划内存时,就应该大一些,越大越好,这样可以预留更多的空间,避免浮动垃圾引起的并发失败。

        如果程序正常运行一段时间后,并没有发现full gc,也就是没有由于老年代的空间不足引起的垃圾回收,那说明老年代空间很充裕,那就先别尝试做老年代的调优了。即使发生了full gc,也应该先尝试新生代调优,包括新生代大小、幸存区大小、晋升阈值等等。这些手段都用了,还是经常发生老年代的full gc,再回过头来看看老年代的设置。看看老年代是超过了多大的内存导致full gc的发生,可以在原有full gc内存的基础上调大1/4~1/3。

        CMSInitiatingOccupancyFraction控制老年代的空间占用达到总内存多少时就采用CMS进行垃圾回收。这个值越低,老年代垃圾回收的触发时机就越早。一般建议设为75%~80%之间,意思是预留20%~25%的空间给那些浮动垃圾。

5.6 案例

  • 案例1 Full GC 和 Minor GC频繁

调大新生代大小。

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

通过查看GC日志,可以看到每个阶段耗费的时间。初始标记和并发标记都是比较快的,重新标记阶段是比较慢的,达到1~2秒,这是因为CMS在重新标记的时候会扫描整个堆内存,不光是要扫描老年代,还要扫描新生代的对象,如果业务高峰时段扫描新生代的对象就会变得特别多。那么能不能在重新标记之前把新生代的对象先进行一次垃圾回收来减少新生代对象的数量,以此来减少扫描新生代对象所耗费的时间呢?开启CMSScavengeBeforeRemark这个参数,发现由1~2秒降低到300ms左右。从而解决了单次暂停时间特别长的问题。

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

之前介绍过,CMS可能会由于空间不足导致并发失败,空间碎片比较多,都可能发生full gc。但在GC日志里,都没有并发失败、碎片过多这样的错误提示,说明老年代空间是充裕的,不是由于老年代的空间不足导致的full gc。在JDK1.7的版本,方法区的实现是永久代,永久代空间不足也会导致full gc,于是得将永久代设置大一点。

本文标签: 学习笔记垃圾JVM