欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 名人名企 > 【JVM面试篇】高频八股汇总——垃圾回收

【JVM面试篇】高频八股汇总——垃圾回收

2025/6/20 21:19:13 来源:https://blog.csdn.net/fhkk55/article/details/148529240  浏览:    关键词:【JVM面试篇】高频八股汇总——垃圾回收

目录

1. 如何判断对象是否死亡/判断垃圾的方法有哪些(两种方法)?

2. 如何判断一个常量是废弃常量?

3. 如何判断一个类是无用的类?

4. 为什么要分为新生代和老年代?

5. 什么是Java里的垃圾回收?如何触发垃圾回收?

6. 垃圾回收算法是什么,是为了解决了什么问题?

7. 垃圾回收算法有哪些?

8. 垃圾回收器有哪些?

9. 标记清除算法的缺点是什么?

10. 垃圾回收算法哪些阶段会stop the world?

11. minorGC、majorGC、fullGC的区别,什么场景触发full GC?

12. 垃圾回收器 CMS 和 G1的区别?

13. 什么情况下使用CMS,什么情况使用G1?

14. G1回收器的特色是什么?

15. GC只会对堆进行GC吗?



1. 如何判断对象是否死亡/判断垃圾的方法有哪些(两种方法)?

引用计数法:

给对象中添加一个引用计数器:

  • 每当有一个地方引用它,计数器就加 1;
  • 当引用失效,计数器就减 1;
  • 任何时候计数器为 0 的对象就是不可能再被使用的。

这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间循环引用的问题。

可达性分析算法:

这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。

哪些对象可以作为 GC Roots 呢?

  • 虚拟机栈(栈帧中的局部变量表)中引用的对象
  • 本地方法栈(Native 方法)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 所有被同步锁持有的对象
  • JNI(Java Native Interface)引用的对象

 

2. 如何判断一个常量是废弃常量?

假如在字符串常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池了。

3. 如何判断一个类是无用的类?

类需要同时满足下面 3 个条件才能算是 “无用的类”

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。

4. 为什么要分为新生代和老年代?

  1. 适应对象生命周期的差异
    在绝大多数 Java 应用程序中,对象的生命周期呈现显著的“朝生夕死”特征。大部分对象(如方法内的局部变量、临时计算结果等)在创建后很快就不再被引用。只有少数对象(如缓存、长期存活的业务对象、Spring 管理的 Bean 等)会存活较长时间。将堆内存划分为新生代和老年代,是为了针对不同生命周期的对象采用最合适的垃圾回收策略

  2. 提升垃圾回收效率(针对新生代)
    新生代存放生命周期短的对象。由于其对象死亡率极高,垃圾回收(Minor GC)发生非常频繁。专门为新生代设计高效的回收算法(如 Copying 算法)成为关键。该算法将新生代划分为 Eden 区和两个 Survivor 区,通过复制存活对象来回收垃圾,其效率非常高(只处理存活对象),且天然解决了内存碎片问题。这种算法在对象快速消亡的场景下效率最优,但需要预留一半空间(Survivor 区)作为复制目标,划分新生代可以控制这部分空间的开销。

  3. 降低老年代回收频率和开销
    老年代存放经过多次 Minor GC 后依然存活的对象(通常由新生代晋升而来)或大对象。这些对象存活时间长,死亡率相对较低。如果老年代与新生代混在一起,频繁的 Minor GC 会扫描整个堆(包括大量存活的老年代对象),造成不必要的开销。独立的老年代意味着 Minor GC 只需扫描较小的新生代,速度更快。同时,老年代的垃圾回收(Major GC / Full GC)发生频率远低于 Minor GC,即使其使用的算法(如 Mark-Sweep-Compact)可能更耗时或会产生停顿,但总体影响在可接受范围内。分代避免了每次回收都对长寿命对象进行无效处理。

  4. 实现空间分配担保
    在 Minor GC 发生之前,JVM 需要确保如果新生代中所有存活的对象都无法放入 Survivor 区(或晋升阈值已到),老年代有足够的空间容纳这些对象。这种机制称为 空间分配担保。划分出明确的老年代区域,使得这种担保机制得以实现,防止 Minor GC 因无法晋升对象而直接失败,进而触发代价高昂的 Full GC。这是分代设计中一个重要的关联保障机制。

  5. 优化内存碎片管理
    新生代采用复制算法,每次 GC 后存活对象被紧凑地复制到 Survivor 区或老年代,基本不存在内存碎片。而老年代对象存活时间长,回收频率低,适合使用 标记-清除或标记-整理 算法。标记-清除会产生碎片,但老年代回收次数少,碎片积累较慢;标记-整理可以解决碎片问题,但整理过程耗时较长。将老年代独立出来,可以专门应对和容忍这种碎片化情况,或者在必要时执行整理,避免新生代频繁 GC 导致整个堆碎片化加剧。

5. 什么是Java里的垃圾回收?如何触发垃圾回收?

垃圾回收(Garbage Collection,GC)是自动管理内存的一种机制,它负责自动释放不再被程序引用的对象所占用的内存,这种机制减少了内存泄漏和内存管理错误的可能性。垃圾回收可以通过多种方式触发,具体如下:

  • 内存不足时:当 JVM 检测到堆内存不足,无法为新的对象分配内存时,会自动触发垃圾回收。
  • 手动请求:虽然垃圾回收是自动的,开发者可以通过调用 System.gc() 或 Runtime.getRuntime().gc() 建议 JVM 进行垃圾回收。不过这只是一个建议,并不能保证立即执行。
  • JVM 参数:启动 Java 应用时可以通过 JVM 参数来调整垃圾回收的行为,比如:-Xmx(最大堆大小)、-Xms(初始堆大小)等。
  • 对象数量或内存使用达到阈值:垃圾收集器内部实现了一些策略,以监控对象的创建和内存使用,达到某个阈值时触发垃圾回收。

6. 垃圾回收算法是什么,是为了解决了什么问题?

JVM垃圾回收机制的原因

在传统编程语言中开发人员要手动分配和释放内存,易导致内存泄漏、内存溢出等问题。而Java作为高级语言,为提供更简单、安全的编程环境,引入垃圾回收机制来自动管理内存。

垃圾回收机制的主要目标

自动检测和回收不再使用的对象,释放其占用内存空间。避免内存泄漏(对象分配内存却无法释放,浪费内存资源),防止内存溢出(程序所需内存超可用内存)。

通过垃圾回收机制带来的好处

JVM可在程序运行时自动识别和清理不再使用的对象,开发人员无需手动管理内存。可提高开发效率、减少错误,使程序更可靠稳定。

7. 垃圾回收算法有哪些?

  • 标记-清除算法:标记-清除算法分为“标记”和“清除”两个阶段,首先通过可达性分析,标记出所有需要回收的对象,然后统一回收所有被标记的对象。标记-清除算法有两个缺陷,一个是效率问题,标记和清除的过程效率都不高,另外一个就是,清除结束后会造成大量的碎片空间。有可能会造成在申请大块内存的时候因为没有足够的连续空间导致再次 GC。

  • 标记-整理算法:复制算法在 GC 之后存活对象较少的情况下效率比较高,但如果存活对象比较多时,会执行较多的复制操作,效率就会下降。而老年代的对象在 GC 之后的存活率就比较高,所以就有人提出了“标记-整理算法”。标记-整理算法的“标记”过程与“标记-清除算法”的标记过程一致,但标记之后不会直接清理。而是将所有存活对象都移动到内存的一端。移动结束后直接清理掉剩余部分。

  • 复制算法:为了解决碎片空间的问题,出现了“复制算法”。复制算法的原理是,将内存分成两块,每次申请内存时都使用其中的一块,当内存不够时,将这一块内存中所有存活的复制到另一块上。然后将然后再把已使用的内存整个清理掉。复制算法解决了空间碎片的问题。但是也带来了新的问题。因为每次在申请内存时,都只能使用一半的内存空间。内存利用率严重不足。

  • 分代回收算法:分代收集是将内存划分成了新生代和老年代。分配的依据是对象的生存周期,或者说经历过的 GC 次数。对象创建时,一般在新生代申请内存,当经历一次 GC 之后如果对还存活,那么对象的年龄+1。当年龄超过一定值(默认是 15,可以通过参数 -XX:MaxTenuringThreshold 来设定)后,如果对象还存活,那么该对象会进入老年代。

8. 垃圾回收器有哪些?

垃圾收集器类型作用域使用算法特点适用场景
Serial串行回收新生代复制算法响应速度优先适用于单核 CPU 环境下的 Client 模式
Serial Old串行回收老年代标记 - 压缩算法响应速度优先适用于单核 CPU 环境下的 Client 模式
ParNew并行回收新生代复制算法响应速度优先多核 CPU 环境中 Server 模式下,与 CMS 配合使用
Parallel Scavenge并行回收新生代复制算法吞吐量优先适用于后台运算,而交互少的场景
Parallel Old并行回收老年代标记 - 压缩算法吞吐量优先适用于后台运算,而交互少的场景
CMS (Concurrent Mark - Sweep)并发回收老年代标记 - 清除算法响应速度优先适用于 B/S 业务,也就是交互多的场景
G1 (Garbage - First)并发,并行回收(此收集器后期优化后并行方式同时存在)新生代&老年代(整堆收集器)复制算法&标记 - 压缩算法响应速度优先面向服务端的应用
  • Serial 收集器(复制算法): 是新生代单线程收集器,标记和清理都是单线程。其优点是简单高效。
  • ParNew 收集器(复制算法): 属于新生代并行收集器,实际上是 Serial 收集器的多线程版本,在多核 CPU 环境下有着比 Serial 更好的表现。
  • Parallel Scavenge 收集器(复制算法): 为新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间 / (用户线程时间 + GC 线程时间) ,高吞吐量可以高效率的利用 CPU 时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景。
  • Serial Old 收集器(标记 - 整理算法): 是老年代单线程收集器,为 Serial 收集器的老年代版本。
  • Parallel Old 收集器(标记 - 整理算法): 是老年代并行收集器,吞吐量优先,为 Parallel Scavenge 收集器的老年代版本。
  • CMS (Concurrent Mark Sweep) 收集器(标记 - 清除算法): 属于老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短 GC 回收停顿时间。
  • G1 (Garbage First) 收集器(标记 - 整理算法): 是 Java 堆并行收集器,G1 收集器是 JDK1.7 提供的一个新收集器,G1 收集器基于 “标记 - 整理” 算法实现,也就是说不会产生内存碎片。此外,G1 收集器不同于之前的收集器的一个重要特点是:G1 回收的范围是整个 Java 堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代 。

9. 标记清除算法的缺点是什么?

主要缺点有两个:

  • 一个是效率问题,标记和清除过程的效率都不高。
  • 另外一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

10. 垃圾回收算法哪些阶段会stop the world?

标记 - 复制算法应用在 CMS 新生代(ParNew 是 CMS 默认的新生代垃圾回收器)和 G1 垃圾回收器中。标记 - 复制算法可以分为三个阶段:

  • 标记阶段:即从 GC Roots 集合开始,标记活跃对象;
  • 转移阶段:即把活跃对象复制到新的内存地址上;
  • 重定位阶段:因为转移导致对象的地址发生了变化,在重定位阶段,所有指向对象旧地址的指针都要调整到对象新的地址上。

下面以 G1 为例,通过 G1 中标记 - 复制算法过程(G1 的 Young GC 和 Mixed GC 均采用该算法),分析 G1 停顿耗时的主要瓶颈。G1 垃圾回收周期如下图所示:

G1的混合回收过程可以分为标记阶段、清理阶段和复制阶段。

标记阶段停顿分析

  • 初始标记阶段:初始标记阶段是指从GC Roots出发标记全部直接子节点的过程,该阶段是STW的。由于GC Roots数量不多,通常该阶段耗时非常短。
  • 并发标记阶段:并发标记阶段是指从GC Roots开始对堆中对象进行可达性分析,找出存活对象。该阶段是并发的,即应用线程和GC线程可以同时活动。并发标记耗时相对长很多,但因为不是STW,所以我们不太关心该阶段耗时的长短。
  • 再标记阶段:重新标记那些在并发标记阶段发生变化的对象。该阶段是STW的。

清理(筛选回收)阶段停顿分析

  • 清理阶段:清理阶段清点出有存活对象的分区和没有存活对象的分区,该阶段不会清理垃圾对象,也不会执行存活对象的复制。该阶段是STW的。

复制阶段停顿分析

  • 复制算法中的转移阶段:需要分配新内存和复制对象的成员变量。转移阶段是STW的,其中内存分配通常耗时非常短,但对象成员变量的复制耗时有可能较长,这是因为复制耗时与存活对象数量与对象复杂程度成正比。对象越复杂,复制耗时越长。

四个STW过程中,初始标记因为只标记GC Roots,耗时较短。再标记因为对象数少,耗时也较短。清理阶段因为内存分区数量少,耗时也较短。转移阶段要处理所有存活的对象,耗时会较长。

因此,G1停顿时间的瓶颈主要是标记-复制中的转移阶段STW。

11. minorGC、majorGC、fullGC的区别,什么场景触发full GC?

在Java中,垃圾回收机制是自动管理内存的重要组成部分。根据其作用范围和触发条件的不同,可以将GC分为三种类型:Minor GC(也称为Young GC)、Major GC(有时也称为Old GC)、以及Full GC。以下是这三种GC的区别和触发场景:

Minor GC (Young GC)

  • 作用范围:只针对年轻代进行回收,包括Eden区和两个Survivor区(S0和S1)。
  • 触发条件:当Eden区空间不足时,JVM会触发一次Minor GC,将Eden区和一个Survivor区中的存活对象移动到另一个Survivor区或老年代(Old Generation)。
  • 特点:通常发生得非常频繁,因为年轻代中对象的生命周期较短,回收效率高,暂停时间相对较短。

Major GC

  • 作用范围:主要针对老年代进行回收,但不一定只回收老年代。
  • 触发条件:当老年代空间不足时,或者系统检测到年轻代对象晋升到老年代的速度过快,可能会触发Major GC。
  • 特点:相比Minor GC,Major GC发生的频率较低,但每次回收可能需要更长的时间,因为老年代中的对象存活率较高。

Full GC

  • 作用范围:对整个堆内存(包括年轻代、老年代以及永久代/元空间)进行回收。
  • 触发条件
    • 直接调用 System.gc() 或 Runtime.getRuntime().gc() 方法时,虽然不能保证立即执行,但JVM会尝试执行Full GC。
    • Minor GC(新生代垃圾回收)时,如果存活的对象无法全部放入老年代,或者老年代空间不足以容纳存活的对象,则会触发Full GC,对整个堆内存进行回收。
    • 当永久代(Java 8之前的版本)或元空间(Java 8及以后的版本)空间不足时。
  • 特点:Full GC是最昂贵的操作,因为它需要停止所有的工作线程(Stop The World),遍历整个堆内存来查找和回收不再使用的对象,因此应尽量减少Full GC的触发。

12. 垃圾回收器 CMS 和 G1的区别?

区别一:使用范围

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

区别二:STW 时间

  • CMS 收集器以最小的停顿时间为目标的收集器;
  • G1 收集器可预测垃圾回收的停顿时间(建立可预测的停顿时间模型)。

区别三:垃圾碎片

  • CMS 收集器是使用“标记 - 清除”算法进行的垃圾回收,容易产生内存碎片;
  • G1 收集器使用的是“标记 - 整理”算法,进行了空间整合,没有内存空间碎片。

区别四:垃圾回收过程

区别五:CMS会产生浮动垃圾

  • CMS 产生浮动垃圾过多时会退化为 serial old,效率低。因为在上图的第四阶段,CMS 清除垃圾时是并发清除的。这个时候,垃圾回收线程和用户线程同时工作会产生浮动垃圾,也就意味着 CMS 垃圾回收器必须预留一部分内存空间用于存放浮动垃圾。
  • 而 G1 没有浮动垃圾,G1 的筛选回收是多个垃圾回收线程并行 gc 的,没有浮动垃圾的回收。在执行“并发清理”步骤时,用户线程也会同时产生一部分可回收对象,但是这部分可回收对象只能在下次执行清理时才会被回收。如果在清理过程中预留给用户线程的内存不足就会出现‘Concurrent Mode Failure’,一旦出现此错误时便会切换到 SerialOld 收集方式。

13. 什么情况下使用CMS,什么情况使用G1?

CMS适用场景:

  • 低延迟需求:适用于对停顿时间要求敏感的应用程序。
  • 老年代收集:主要针对老年代的垃圾回收。
  • 碎片化管理:容易出现内存碎片,可能需要定期进行 Full GC 来压缩内存空间。

G1适用场景:

  • 大堆内存:适用于需要管理大内存堆的场景,能够有效处理数 GB 以上的堆内存。
  • 对内存碎片敏感:G1 通过紧凑整理来减少内存碎片,降低了碎片化对性能的影响。
  • 比较平衡的性能:G1 在提供较低停顿时间的同时,也保持了相对较高的吞吐量。

14. G1回收器的特色是什么?

G1的特点:

  • G1最大的特点是引入分区的思路,弱化了分代的概念。
  • 合理利用垃圾收集各个周期的资源,解决了其他收集器、甚至CMS的众多缺陷。

G1相比较CMS的改进:

  • 算法:G1基于标记 - 整理算法,不会产生空间碎片,在分配大对象时,不会因无法得到连续的空间,而提前触发一次FULL GC。
  • 停顿时间可控:G1可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象。
  • 并行与并发:G1能更充分的利用CPU多核环境下的硬件优势,来缩短stop the world的停顿时间。

15. GC只会对堆进行GC吗?

JVM 的垃圾回收器不仅仅会对堆进行垃圾回收,它还会对方法区进行垃圾回收。

  • 堆(Heap)

堆是用于存储对象实例的内存区域。大部分的垃圾回收工作都发生在堆上,因为大多数对象都会被分配在堆上,而垃圾回收的重点通常也是回收堆中不再被引用的对象,以释放内存空间。

  • 方法区(Method Area)

方法区是用于存储类信息、常量、静态变量等数据的区域。虽然方法区中的垃圾回收与堆有所不同,不过同样存在对不再需要的常量、无用的类信息等进行清理的过程。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词