深入理解Java虚拟机(二)——垃圾回收器

本文探讨了JVM中的垃圾回收机制,包括触发条件、如何判断垃圾对象、不同垃圾回收区域、引用计数法到标记-压缩算法的对比,以及分代收集策略和各垃圾回收器的详细介绍。此外,还涵盖了如何分析和解读GC日志,帮助开发者优化性能。
摘要由CSDN通过智能技术生成

一、垃圾回收

1. JVM中会在以下情况触发垃圾回收

  • 对象没有被引用

  • 作用域发生未捕捉异常

  • 程序正常执行完毕

  • 程序执行了System.exit()

  • 程序发生意外终止

2. 如何确定对象为垃圾对象?

  • 引用计数法:此方法无法解决两个对象之间循环引用问题

  • 可达性分析:图的遍历,基本思路:

    • 可达性分析算法是以根对象集合GCRoots为起始点,按照从上至下方式搜索被根对象集合所连接的目标对象是否可达
    • 使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链Reference Chain
    • 如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象
    • 在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象

    哪些元素可以作为GCRoots

    • 虚拟机栈中引用的对象

      比如:各个线程被调用的方法中使用到的参数、局部变量等

    • 本地方法栈内JNI(通常说的本地方法)引用的对象

    • 方法区中类静态属性引用的对象

      比如:字符串常量池(StringTable)里的引用

    • 所有被同步锁synchronized持有的对象

    • Java虚拟机内部的引用

      基本数据类型对应的Class对象,一些常驻的异常对象(如:NullPointerExceptionOutofMemoryError),系统类加载器

3. 垃圾回收区域

在这里插入图片描述

垃圾回收器可以对年轻代回收,也可以对老年代回收,甚至是全堆和方法区的回收。其中,Java堆是垃圾收集器的工作重点。从次数上讲:频繁收集Young区,较少收集Old区,基本不动Perm

4. 垃圾收集算法

1. 引用计数法

对每个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况。

对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1,只要对象A的引用计数器的值为0,即表示对象A不可能再被使用,可进行回收

  • 优点:实现简单,垃圾对象便于辨识,判定效率高,回收没有延迟性
  • 缺点:
    • 它需要单独的字段存储计数器,这样的做法增加了存储空间的开销
    • 每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销
    • 引用计数器有一个严重的问题,即无法处理循环引用的情况。这是一条致命缺陷导致在Java的垃圾回收器中没有使用这类算法

2. 复制算法

复制算法:该算法是从根集合扫描,并将存活的对象复制到新的空间,这种算法在存活对象少时比较高效。

  • 优点
    • 没有标记和清除过程,实现简单,运行高效
    • 复制过去以后保证空间的连续性,不会出现碎片问题
  • 缺点
    • 此算法的缺点也是很明显的,就是需要两倍的内存空间
    • 对于G1这种分拆成为大量regionGC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小

如果系统中的垃圾对象很多,复制算法不会很理想,因为复制算法需要复制的存活对象数量并不会太大,或者说非常低才行。

在这里插入图片描述

3. 标记-清除算法

标记 - 清除算法(Mark-Sweep:该算法是从根集合扫描整个空间,标记存活的对象,然后在扫描整个空间对没有被标记的对象进行回收。这种算法在存活对象较多时比较高效,但会产生内存碎片,缺点:

  • 效率不算高
  • 在进行GC的时候,需要停止整个应用程序,导致用户体验差
  • 这种方式清理出来的空闲内存是不连续的,产生内存碎片,需要维护一个空闲列表

在这里插入图片描述

4. 标记-压缩算法

标记 - 压缩算法(Mark-Compress:标记整理算法和标记清除算法一样都会扫描并标记存活对象,在回收未标记对象的同时会整理被标记的对象,解决了内存碎片的问题

  • 执行过程
    • 第一阶段和标记清除算法一样从根节点开始标记所有被引用对象
    • 第二阶段将所有的存活对象压缩到内存的一端,按顺序排放
    • 之后,清理边界外所有的空间

在这里插入图片描述

标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理,因此,也可以把它称为标记-清除-压缩(Mark-Sweep- Compact)算法

二者的本质差异在于标记-清除算法是一种非移动式的回收算法,标记-压缩是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策

5. 三种算法对比

标记-清除标记-压缩复制算法
速度中等最慢最快
空间开销少(但会堆积碎片)少(不堆积碎片)通常需要活对象的2倍大小(不堆积碎片)
移动对象
  • 效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存
  • 而为了尽量兼顾上面提到的三个指标,标记-整理算法相对来说更平滑一些,但是效率上不尽如人意,它比复制算法多了一个标记的阶段,比标记-清除多了一个整理内存的阶段。

5. 分代收集

JVM中,不同的内存区域作用和性质不一样,使用的垃圾回收算法也不一样,所以JVM中又定义了几种不同的垃圾回收器,图中连线代表两个回收器可以同时使用

在这里插入图片描述

新生代收集器:SerialParNewParallel Scavenge

老年代收集器:Serial OldParallel OldCMS

整堆收集器:G1

上图说明:

  • 两个收集器有连线,表明它们可以搭配使用:
    Serial/Serial Old、Serial/CMS、ParNew/Serial Old、ParNew/CMS、ParallelScavenge/Serial Old、Parallel Scavenge/Parallel Old、G1
    • (红色虚线)由于维护和兼容性测试的成本,在JDK8时将Serial+CMS
      ParNew+Serial Old这两个组合声明为废弃,并在JDK9中完全取消了这些组合的支持,即移除。
  • (绿色虚线)JDK14中弃用ParallelScavenge和Serial0ldGC组合
    • (青色虚线)JDKI14中删除CMS垃圾回收器

1. Serial GC

Serial GC。从名字上看,串行GC意味着是一种单线程的,所以它要求收集的时候所有的线程暂停(Stop The World)。这对于高性能的应用是不合理的,所以串行GC一般用于Client模式的JVM中。(过时)
在这里插入图片描述

2. ParNew GC

ParNew GC。并行回收器。是在SerialGC的基础上,增加了多线程机制。但是如果机器是单CPU的,这种收集器是比SerialGC效率低的。

Par是Parallel的缩写,New表示只能处理新生代

ParNew收集器除了采用并行回收的方式执行内存回收外,两款垃圾收集器之间几乎没有任何区别

ParNew收集器在年轻代中同样也是采用复制算法、Stop-the-World机制

ParNew是很多JVM运行在Server模式下新生代的默认垃圾收集器

在这里插入图片描述

对于新生代,回收次数频繁,使用并行方式高效

对于老年代,回收次数少,使用串行方式节省资源。(CPU并行需要切换线程,串行可以省去切换线程的资源)

3. Parrallel Scavenge GC

Parrallel Scavenge GC。这种收集器又叫吞吐量优先收集器,而吞吐量 = 程序运行时间/(JVM执行回收的时间+程序运行时间)。假设程序运行了100分钟,JVM的垃圾回收占用1分钟,那么吞吐量就是99%Parallel Scavenge GC由于可以提供比较不错的吞吐量,所以被作为了server模式JVM的默认配置。

4. Parallel Old GC

ParallelOld是老生代并行收集器的一种,使用了标记整理算法,是JDK1.6中引进的,在之前老生代只能使用串行回收收集器。

5. Serial Old GC

Serial Old是老生代client模式下的默认收集器,单线程执行,同时也作为CMS收集器失败后的备用收集器。(过时)

6. CMS GC

CMS(Concurrent-Mark-Sweep)又称响应时间优先回收器,使用标记清除算法,并且也会stop-the-world。它的回收线程数为(CPU核心数+3)/4,所以当CPU核心数为2时比较高效些。CMS分为4个过程:初始标记、并发标记、重新标记、并发清除。

在这里插入图片描述

在JDK14中,删除了CMS垃圾回收器

7. G1 GC

GarbageFirst(G1)。比较特殊的是G1回收器既可以回收Young Generation,也可以回收Tenured Generation。它是在JDK6的某个版本中才引入的,性能比较高,同时注意了吞吐量和响应时间。

对于垃圾收集器的组合使用可以通过下表中的参数指定:

指定方式新生代GC方式老年代GC方式
-XX:+UseSerialGC串行GC串行GC
-XX:+UseParallelGC并行GC并行GC
-XX:+UseConcMarkSweepGC并行GC并发GC
-XX:+UseParNewGC并行GC串行GC
-XX:+UseParallelOldGC并行GC并行GC
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
串行GC并发GC

默认的GC种类可以通过jvm.cfg或者通过jmap dumpheap来查看,一般我们通过jstat -gcutil [pid] 1000可以查看每秒gc的大体情况,或者可以在启动参数中加入:-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:./gc.log来记录GC日志。

8. 垃圾回收器总结

垃圾收集器分类作用位置使用算法特点
Serial串行运行新生代复制算法响应速度优先
ParNew并行运行新生代复制算法响应速度优先
Parallel并行运行新生代复制算法吞吐量优先
Serial Old串行运行老年代标记-压缩算法响应速度优先
Parallel Old并行运行老年代标记-压缩算法吞吐量优先
CMS并发运行老年代标记-清除算法响应速度优先
G1并发、并行运行新生代、
老年代
标记-压缩算法
复制算法
响应速度优先

二、GC日志

1. 开启GC日志

  • -XX:+PrintGC

输出GC日志。类似:-verbose:gc

  • -XX:+PrintgcDetails

输出GC的详细日志

  • -xX:+PrintGCTimeStamps

输出GC的时间戳,以基准时间的形式

  • -xx:+PrintGCDatestamps

输出GC的时间,以日期的形式,如2021-12-26T21:53:59234+0800

  • -XX:+PrintHeapAtGo

在进行GC的前后打印出堆的信息

  • -Xloggc:./logs/gc.log

日志文件的输出路径

2. GC日志分析

1. verbose:gc

  • 打开GC日志:-verbose:gc

  • 查看GC内容:
    在这里插入图片描述

  • GC内容解析

    • GCFull GCGC的类型,GC只在新生代上进行,Full GC包括永生代, 新生代, 老年代

    • Allocation FailureGC发生的原因

    • 80832K->19298K:堆在GC前的大小和GC后的大小。228840k: 现在堆的总大小

    • 0.0084018 secsGC持续的时间

2. -XX:PrintGCDetails

  • 打开GC日志:-verbose:gc -XX:PrintGCDetails
  • 查看GC内容:

在这里插入图片描述

  • GC内容解析
    • GC,Full FC:同样是GC的类型
    • Allocation FailureGC原因
    • PSYoungGen:使用了Parallel Scavenge并行垃圾收集器的新生代GC前后大小的变化
    • ParoldGen:使用ParallelOld并行垃圾收集器的老年代GC前后大小的变化
    • Metaspace:元空间GC前后大小的变化,JDK1.8中引入了元空间以替代永久代
    • xxx secs:指GC花费的时间
    • Times:user:指的是垃圾收集器花费的所有CPU时间,sys花费在等待系统调用或系统事件的时间, real表示GC从开始到结束的时间,包括其他进程占用时间片的实际时间

3. 日志补充说明

  • [GC[Full GC说明了这次垃圾收集的停顿类型,如果有Full则说明GC发生了Stop The World
  • 使用Serial收集器在新生代的名字是DefaultNewGeneration,因此显示的是[DefNew
  • 使用ParNew收集器在新生代的名字会变成[ParNew,意思是Parallel New Generation
  • 使用ParallelScavenge收集器在新生代的名字是[PSYoungGen
  • 老年代的收集和新生代道理一样,名字也是收集器决定的
  • 使用G1收集器的话,会显示为garbage-first heap
  • Allocation Failure表明本次引起GC的原因是因为在年轻代中没有足够的空间能存储新的数据了
  • [PSYoungGen:5986K->696K(8704K)]5986K->704K(9216K)中括号内:GC回收前年轻代大小,回收后大小,(年轻代总大小)
    括号外:GC回收前年轻代和老年代大小,回收后大小,(年转代和老年代总大小)
  • user代表用户态回收耗时,sys内核态回收耗时,real实际耗时。由于多核的原因,时间总和可能会超过real时间

4. Minor GC日志

在这里插入图片描述

5. Full GC 日志

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

止步前行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值