JVM-【面试题】-垃圾收集算法+垃圾收集器,以后就不用担心对象那些事了

news/2024/10/30 17:20:01/

一、垃圾收集算法

在jvm里对可回收的对象在不同的垃圾收集器里,有不同的回收算法,具体的可以分为这四种:分代收集算法、复制算法、标记清除算法、标记整理算法

1.1 分代收集算法

当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。

比如在新生代中,每次收集都会有大量对象(近99%)死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。

注意:“标记-清除”或“标记-整理”算法会比复制算法慢10倍以上。

1.2 标记-复制算法

为了解决效率问题,“复制”收集算法出现了。它将内存分成大小相同的两块,每次只使用其中一块。当这块的内存使用完后,就将存活的对象复制到另外一块去,然后再把使用的空间一次清理掉。这样就使得每一次的内存回收都是对内存区间的一半进行回收。

这种算法是使用在新生代,因为新生代的对象都是朝生夕死,非常适合使用复制算法。

1.3 标记-清除算法

算法分为“标记”和“清除”阶段:标记存活的对象, 统一回收所有未被标记的对象(一般选择这种);也可以反过来,标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象 。它是最基础的收集算法,比较简单,但是会带来两个明显的问题:

  1. 效率问题 (如果需要标记的对象太多,效率不高)

  1. 空间问题(标记清除后会产生大量不连续的碎片)

1.4 标记-整理算法

根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。

二、垃圾收集器

本次只介绍jdk1.8常用到的垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。

虽然我们对各个收集器进行比较,但并非为了挑选出一个最好的收集器。因为直到现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,我们能做的就是根据具体应用场景选择适合自己的垃圾收集器。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的Java虚拟机就不会实现那么多不同的垃圾收集器了。

2.1 Serial收集器

参数:-XX:+UseSerialGC(年轻代) -XX:+UseSerialOldGC(老年代)

Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( "Stop The World" ),直到它收集结束。

新生代采用复制算法,老年代采用标记-整理算法

虚拟机的设计者们当然知道Stop The World带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。

但是Serial收集器有没有优于其他垃圾收集器的地方呢?当然有,它简单而高效(与其他收集器的单线程相比)。Serial收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。

Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案

2.2 Parallel Scavenge收集器(jdk1.8默认垃圾收集器)

参数:-XX:+UseParallelGC(年轻代)、-XX:+UseParallelOldGC(老年代)

Parallel收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器类似。默认的收集线程数跟cpu核数相同,当然也可以用参数(-XX:ParallelGCThreads)指定收集线程数,但是一般不推荐修改。

Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。 Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。

新生代采用复制算法,老年代采用标记-整理算法。

Parallel Old收集器是Parallel Scavenge收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器(JDK8默认的新生代和老年代收集器)。

2.3 ParNew收集器

参数:-XX:+UseParNewGC(年轻代)

ParNew收集器其实跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用。

新生代采用复制算法,老年代可以配合其他垃圾收集器

它是许多运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,只有它能与CMS收集器配合工作

1.4 CMS收集器

参数:-XX:+UseConcMarkSweepGC(老年代)

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。

从CMS收集器是一种“标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:

初始标记: 暂停所有的其他线程(STW),并记录下gc roots直接能引用的对象,速度很快。

并发标记: 并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程, 这个过程耗时较长但是不需要停顿用户线程, 可以与垃圾收集线程一起并发运行。因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变。

重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录(主要是处理漏标问题),这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。主要用到三色标记里的增量更新算法(见下面详解)做重新标记。

并发清理: 开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑色不做任何处理(见下面三色标记算法详解)。

并发重置:重置本次GC过程中的标记数据。

从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面几个明显的缺点:

  • 对CPU资源敏感(会和服务抢资源);

  • 无法处理浮动垃圾(在并发标记和并发清理阶段又产生垃圾,这种浮动垃圾只能等到下一次gc再清理了);

  • 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生,当然通过参数-XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理

  • 执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况,特别是在并发标记和并发清理阶段会出现,一边回收,系统一边运行,也许没回收完就再次触发full gc,也就是"concurrent mode failure",此时会进入stop the world,用serial old垃圾收集器来回收

CMS的相关核心参数

  1. -XX:+UseConcMarkSweepGC:启用cms

  1. -XX:ConcGCThreads:并发的GC线程数

  1. -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)

  1. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次

  1. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)

  1. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整

  1. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,降低CMS GC标记阶段(也会对年轻代一起做标记,如果在minor gc就干掉了很多对垃圾对象,标记阶段就会减少一些标记时间)时的开销,一般CMS的GC耗时 80%都在标记阶段

  1. -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW

  1. -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;


http://www.ppmy.cn/news/12213.html

相关文章

每日一问-ChapGPT-20230114-关于小年

文章目录每日一问-ChapGPT系列起因每日一问-ChapGPT-20230114-关于小年腊月每天都做些什么的歌谣为什么现在的年味淡了很多,感觉不到过年为什么春节放假要调休,不能多放几天吗说说现在世界上极端气候,以及多少年后,地球存在不适宜…

P3654 First Step (ファーストステップ)

P3654 First Step (ファーストステップ) 题目背景 知らないことばかりなにもかもが(どうしたらいいの?) 一切的一切 尽是充满了未知数(该如何是好) それでも期待で足が軽いよ(ジャンプだ!&…

如何避免内存溢出和频繁的垃圾回收

0 OOM和频繁GC预防方案 代码明明简单,日常跑没问题,怎么一大促就卡死甚至进程挂掉?大多因为设计时,就没针对高并发、高吞吐量case考虑过内存管理。 1 自动内存管理机制的实现原理 内存管理主要考虑: 1.1 申请内存 …

十九、命令模式 ( Command Pattern )

命令模式(Command Pattern)中请求以命令的形式包裹在对象中,并传给调用对象 调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令 命令模式是行为型模式,一种数据驱动的设计…

PCI、PCI-X、PCI-E、PCI-E Card、Mini PCI-E、M.2、Add-in Card 这些概念你搞清楚了吗

搞硬件或通信的“攻城狮”们,免不了要和各种通信协议及接口打交道。比如,我们经常接触PCI、PCI-X、PCI-E、PCI-E Card、Mini PCI-E、M.2(NGFF)、Add-in Card这些概念,作为“攻城狮”队伍中的一员,你搞清楚它们之间的关系了吗&…

JUC(java.util.concurrent)的常见类

文章目录一、JUC常见类Callable 接口ReentrantLockSemaphore(信号量)CountDownLatch一、JUC常见类 concurrent代表了并发,这个包下为我们提供了并发编程(多线程)相关的组件. Callable 接口 我们的Callable接口和Runnable是一样的,但也有一些区别: Run…

IIC通信协议

数据有效性 IC由两条线组成,一条双向串行数据线SDA,一条串行时钟线SCL。 SDA线上的数据必须在时钟的高电平周期保持稳定,数据线的高或低电平状态只有在 SCL 线的时钟信号是低电平时才能改变。 换言之, SCL为高电平时表示有效数据…

javaEE 初阶 — java对于的操作文件

文章目录1. File 类概述2. 代码示例2.1 示例1:以绝对路径为例,演示获取文件路径2.2 示例2:以相对路径为例,演示获取文件路径2.3 示例3:测试文件是否存在、测试是不是文件、测试是不是目录2.4 示例4:创建文件…