备战面试日记(2.5) - (JVM.垃圾收集器&GC日志)

本人本科毕业,21届毕业生,一年工作经验,简历专业技能如下,现根据简历,并根据所学知识复习准备面试。

记录日期:2022.1.2

大部分知识点只做大致介绍,具体内容根据推荐博文链接进行详细复习。

JVM - 垃圾收集器

参考《深入理解JVM虚拟机》第三版3.5章节,建议详细阅读书籍。

参考博客链接:七大经典垃圾回收器篇+部分调优 不会让你失望(两万字)【图文】

按工作方式来分:

  • 串行回收器: Serial、Serial Old。
  • 并行回收器: ParNew、 Parallel Scavenge、Parallel Old。
  • 并发回收器: CMS、G1。

按回收区域来分:

  • 新生代收集器: Serial、ParNew、 Parallel Scavenge。
  • 老年代收集器: Serial 0ld、Parallel 0ld、 CMS。
  • 整堆收集器: G1。

各款经典垃圾收集器的组合关系如下图:

在这里插入图片描述

**jdk8默认的垃圾收集器是:**Parallel Scavenge + Parallel Old,一般生产环境都是:ParNew + CMS + Serial Old。

-XX:+PrintCommandLineFlags 参数可查看默认设置收集器类型。
-XX:+PrintGCDetails亦可通过打印的GC日志的新生代、老年代名称判断。

垃圾收集器

Serial收集器

Serial收集器是最基本、历史最悠久的垃圾收集器了,JDK1.3之前回收新生代唯一的选择。

它是一个单线程收集器,它不仅只会使用一个处理器或一条收集线程区完成垃圾收集的工作,更重要的是强调它进行垃圾收集时,必须”Stop The World“。

在这里插入图片描述

优点

与其他收集器的单线程相比,Serial收集器简单而高效,对于内存资源受限的环境,它是所有收集器里额外内存消耗最小的,对于单核处理器或者处理器核心数较少的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。

Serial收集器对于运行在客户端模式下的虚拟机是一种很好的选择。

ParNew收集器

实质上是Serial收集器的多线程并行版本。

在这里插入图片描述

除了Serial收集器外,目前只有它能与CMS收集器配合工作。

Parallel Scavenge收集器

新生代收集器,基于标记-复制算法实现,并行收集的多线程收集器。
Parallel Scavenge收集器的目标是达到一个可控制的吞吐量

所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值,即:

在这里插入图片描述

Serial Old收集器

Serial收集器的老年代版本,单线程收集器,标记-整理算法。

目前可能两种用途:

  • JDK5以及之前版本,与Parallel Scavenge收集器搭配使用。
  • 作为CMS收集器发生失败时的后备方案。

在这里插入图片描述

Parallel Old收集器

Parallel Scavenge收集器的老年代版本,支持多线程并发收集,标记-整理算法。

在这里插入图片描述

CMS收集器(重点)

参考博客链接:JVM 面试必问的 CMS,你懂了吗?

CMS收集器Concurrent Mark-Sweep Collector),从名字上可以看出两点,一个是使用的是并发收集,第二个是使用的收集算法是"标记-清除"算法。从而也可以推测出该收集器的特点是低延迟并且会有浮动垃圾的问题。下面详细介绍一下这个收集器的特点。

回收过程

CMS收集器整个过程分为以下四个步骤:

  1. 初始标记(Initial-Mark)
  2. 并发标记(Concurrent-Mark)
  3. 重新标记(Remark)
  4. 并发清除(Concurrent-Sweep)

其中”初始标记“”重新标记“需要”Stop The World“。

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

初始标记

初始标记(Initial-Mark) 阶段,在这个阶段中,程序中所有的工作线程都将会因为“Stop-the-World”机制而出现短暂的暂停,这个阶段的主要任务仅仅只是标记出GCRoots能直接关联到的对象。一旦标记完成之后就会恢复之前被暂停的所有应用线程。由于直接关联对象比较小,所以这里的速度非常快

并发标记

并发标记(Concurrent-Mark) 阶段:从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。

重新标记

重新标记(Remark)阶段:由于在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那-部分对象的标记记录见2.4的增量更新),这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短。

并发清除

并发清除(Concurrent-Sweep)阶段:此阶段清理删除掉标记阶段判断的已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。

CMS优点
  • 并发收集
  • 低延迟
CMS缺点
  1. 产生内存碎片,导致并发清除后,用户线程可用的空间不足。在无法分配大对象的情况下,不得不提前触发Full GC。
  2. CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。(默认回收线程数是(处理器核心数量+3)/ 4
  3. CMS收集器无法处理浮动垃圾。可能出现“Concurrent Mode Failure"失败而导致另一次Full GC的产生。在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或者交叉运行的,那么在并发标记阶段如果产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收,从而只能在下一次执行GC时释放这些之前未被回收的内存空间。
CMS可以设置的参数
  1. -XX: +UseConcMarkSweepGC:手动指定使用CMS收集器执行内存回收任务。

    ➢ 开启该参数后会自动将-XX: +UseParNewGC打开。即: ParNew (Young区用) +CMS (0ld区用) +Serial 0ld的组合。

  2. -XX:CMSlnitiatingOccupanyFraction:设置堆内存使用率的阈值,一旦达到该阈值,便开始进行回收。

    ➢ JDK5及以前版本的默认值为68,即当老年代的空间使用率达到68%时,会执行一次CMS回收。JDK6及以上版本默认值为92%

    ➢ 如果内存增长缓慢,则可以设置一个稍大的值,大的阈值可以有效降低CMS的触发频率,减少老年代回收的次数可以较为明显地改善应用程序性能。反之,如果应用程序内存使用率增长很快,则应该降低这个阈值,以避免频繁触发老年代串行收集器(Serial)。因此通过该选项便可以有效降低Full GC的执行次数。

  3. -XX: +UseCMSCompactAtFullCollection:用于指定在执行完Full GC后对内存空间进行压缩整理,以此避免内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变得更长了。

  4. -XX: CMSFullGCsBeforeCompaction:设置在执行多少次Full GC后对内存空间进行压缩整理

  5. -XX: ParallelCMSThreads:设置CMS的线程数量

G1收集器(重点)

Garbage First(简称G1)收集器,开创了收集器面向局部收集的设计思路基于Region的内存布局形式。

G1介绍
  1. G1是一款主要面向服务端应用的垃圾收集器,G1可以面向堆内存任何部分来组成回收集进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。
  2. G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间、或者老年代空间。
    Region中还有一类特殊的Humongous区域,专门用来存储大对象。
  3. G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为单次回收的最小单元,既每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。更具体的处理思路是让G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间,优先处理回收价值收益最大的那些Region
  4. G1收集器在JDK1.7版本正式启用,移除了Experimental的标识,是JDK 9以后的默认垃圾回收器,取代了CMS回收器以及Parallel + Parallel 0ld组合。被Oracle官方称为“全功能的垃圾收集器”。

CMS已经在JDK 9中被标记为废弃(deprecated),JDK14完全清除掉了。G1在jdk8中还不是默认的垃圾回收器,需要使用-XX: +UseG1GC来启用。G1在jdk9中成为了默认的垃圾回收器。

在这里插入图片描述

G1的细节问题
将Java堆分成多个独立Region后,Region里面存在的跨Region引用对象如何解决?

G1的记忆集在存储结构的本质上是一种哈希表,Key是别的Region的起始地址,Value是一个集合,里面存储的元素是卡表的索引号,这种互相指向的卡表比原来的实现更复杂,更占用内存。

在并发标记阶段如何保证收集线程与用户线程不干扰运行?

首先,G1是通过原始快照(SATB)算法来实现。

此外,也垃圾回收对线程的影响也体现在对象的内存分配上,G1在每一个Region中都设计了两个名为TAMS(Top at Mark Start)的指针,把Region中的一部分空间划分出来用于并发回收过程中的新对象分配。并发回收时新分配的对象都必须要在这两个指针位置以上,这些对象被默认为隐式标记过的,即默认他们时存活的,不纳入回收范围。

怎样建立起可靠的停顿预测模型?

G1的停顿预测模型是以衰减均值为理论基础来实现的,在垃圾回收的过程中,G1会记录每个Region的回收耗时,每个Region记忆集里的脏卡数量等各个可测量的步骤花费的成本,并分析得出平均值、标准偏差、置信度等信息。

G1会根据这些统计信息预测现在开始回收的话,由哪些Region组成的回收集才可以在不超过期望停顿时间的约束下获得最高的收益。

为什么G1使用原始快照(STAB),CMS使用增量更新?

对于region(区域),SATB相对增量更新效率会高(当然SATB可能造成更多的浮动垃圾),因为不需要在重新标记阶段再次深度扫描被删除引用对象。
而CMS对增量引用的根对象会做深度扫描。
G1因为很多对象都位于不同的region,CMS就一块老年代 区域,重新深度扫描对象的话G1的代价会比CMS高,所以G1选择SATB不深度扫描对象,只是简单标记,等到下一轮GC 再深度扫描。

回收过程

G1收集器整个过程分为以下四个步骤:

  1. 初始标记(Initial Marking)
  2. 并发标记(Concurrent Marking)
  3. 最终标记(Final Marking)
  4. 筛选回收(Live Data Counting and Evacuation)

其中“初始标记”“最终标记”“筛选回收”需要”Stop The World“。

初始标记

仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且时借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际上并没有额外的停顿。

并发标记

从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。

最终标记

对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。

筛选回收

**负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。**这里的操作涉及存活对象的移动,必须暂停用户线程,由多条收集器线程并行完成的。

G1优点
  • 可以指定最大停顿时间。
  • 分Region的内存布局。
  • 按收益动态确定回收集。
  • G1从整体来看时基于“标记-整理”算法来实现的收集器,从局部上看又是基于“标记-复制”算法实现的,这两种算法都意味着G1运行期间不会产生内存空间碎片,垃圾收集完成后能提供规整的可用内存。这种特性有利于程序长时间运行,在程序为大对象分配内存时不容易因无法找到连续内存空间而提前触发下一次收集。
什么情况下考虑使用G1?
  • 实时数据占用超过一半的堆空间
  • 对象分配或者晋升的速度变化大
  • 希望消除长时间的GC停顿(超过0.5-1秒)
G1和CMS的区别?(面试问)
区别一: 使用范围不一样

CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用
G1收集器收集范围是老年代和新生代。不需要结合其他收集器使用

区别二: STW的时间

CMS收集器以最小的停顿时间为目标的收集器。

G1收集器可预测垃圾回收的停顿时间(建立可预测的停顿时间模型)

区别三: 垃圾碎片

CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片

G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。

区别四: 垃圾回收的过程不一样

见CMS 和 G1 的回收过程。

Shenandoah收集器

这里先不做记录,见3.6.1节。

ZGC收集器

这里先不做记录,见3.6.2节。

GC日志(见链接)

参考博客链接:带你读懂Java GC日志信息 教你如何使用工具查看【图文演示】

GC日志的参数

  • -XX:+PrintGC:输出GC日志
  • -XX:+PrintGCDetails:输出GC的详细日志
  • -XX:+PrintGCTimeStamps:输出GC的时间戳(以基准时间的形式)
  • -XX:+PrintGCDateStamps:输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
  • -XX:+PrintHeapAtGC:在进行GC的前后打印出堆的信息
  • -Xloggc:…/logs/gc.log:日志文件的输出路径
  • -XX:+PrintCommandLineFlags:查看JDK不同版本使用的垃圾回收器
  • -Xms600m -Xmx600m:初始化堆内存大小和最大堆内存大小
  • -XX:MetaspaceSize=500m -XX:MaxMetaspaceSize=500m:初始化元空间大小和最大元空间大小

测试

以下内容均在jdk1.8下测试。

-XX:+PrintCommandLineFlags

查看JDK不同版本使用的垃圾回收器。

输出:

-XX:InitialHeapSize=232723392 -XX:MaxHeapSize=3723574272 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
-XX:+PrintGCDetails

输出GC的详细日志(常用)。

输出:

Heap
 PSYoungGen      total 66560K, used 5735K [0x0000000776000000, 0x000000077aa00000, 0x00000007c0000000)
  eden space 57344K, 10% used [0x0000000776000000,0x0000000776599e80,0x0000000779800000)
  from space 9216K, 0% used [0x000000077a100000,0x000000077a100000,0x000000077aa00000)
  to   space 9216K, 0% used [0x0000000779800000,0x0000000779800000,0x000000077a100000)
 ParOldGen       total 151552K, used 0K [0x00000006e2000000, 0x00000006eb400000, 0x0000000776000000)
  object space 151552K, 0% used [0x00000006e2000000,0x00000006e2000000,0x00000006eb400000)
 Metaspace       used 3194K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 345K, capacity 388K, committed 512K, reserved 1048576K

内存分配与回收策略

Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题:自动给对象分配内存以及自动回收分配给对象的内存。关于回收内存这方面,前面已经使用了大量篇幅去介绍虚拟机中的垃圾收集器体系以及运作原理,现在后面再一起来探讨一下给对象分配内存的事情。

对象的内存分配,往大方向讲,就是在堆上分配但也可能经过JIT编译后被拆散为标量类型并间接地栈上分配),对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配。少数情况下也可能会直接分配在老年代中,分配的规则并不是百分之百固定的,其细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数的设置。

这部分建议直接阅读《深入理解JVM虚拟机》3.8节。
也可以参考博客链接,也是根据书来写的:Java虚拟机:内存分配与回收策略

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

舍其小伙伴

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

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

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

打赏作者

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

抵扣说明:

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

余额充值