[Java 源码] 秋招常被问到 GC 相关的几道面试题(集中在分配以及回收)

简介: [Java 源码] 秋招常被问到 GC 相关的几道面试题(集中在分配以及回收)

垃圾回收,顾名思义就是释放垃圾占用的空间,从而提升程序性能,防止内存泄露。当一个对象不再被需要时,该对象就需要被回收并释放空间。

Java 内存运行时数据区域包括程序计数器、虚拟机栈、本地方法栈、堆等区域。其中,程序计数器、虚拟机栈和本地方法栈都是线程私有的,当线程结束时,这些区域的生命周期也结束了,因此不需要过多考虑回收的问题。而堆是虚拟机管理的内存中最大的一块,堆中的内存的分配和回收是动态的,垃圾回收主要关注的是堆空间。


虽然现在 jdk21 虚拟线程出来了,jdk17 使用人数也直线上升,但是面试还是jdk8 ,懂得都懂!!!


0. 总结

其实一套组合拳下来,问的就是是什么、为什么、怎么办

什么是垃圾回收以及什么是垃圾,怎么判断对象是垃圾,为什么说它是垃圾等等


1. 内存分配原则

主要有一下 3 条原则。

1. 对象优先在 Eden 区分配

大多数情况下,对象在新生代中 Eden 区分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。执行 Minor GC 后,后面分配的对象如果能够存在 Eden 区的话,还是会在 Eden 区分配内存。

2. 大对象直接进入老年代

大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。

大对象直接进入老年代主要是为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。

3. 长期存活的对象将进入老年代

既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。

大部分情况,对象都会首先在 Eden 区域分配。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间(s0 或者 s1)中,并将对象年龄设为 1(Eden 区->Survivor 区后对象的初始年龄变为 1)。

对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

2. 内存回收原则

针对 HotSpot VM 的实现,它里面的 GC 其实准确分类只有两大种:

部分收集 (·Partial GC):

● 新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;

● 老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集;

● 混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。

整堆收集 (Full GC):收集整个 Java 堆和方法区。

3. 空间分配担保的目的是什么

空间分配担保是为了确保在 Minor GC 之前老年代本身还有容纳新生代所有对象的剩余空间。

4. 与垃圾回收有关的方法

  1. gc
    调用垃圾回收器的方法是 gc,该方法在 System 类和 Runtime 类中都存在。
    ● 在 Runtime 类中,方法 gc 是实例方法,方法 System.gc 是调用该方法的一种传统而便捷的方法。
    ● 在 System 类中,方法 gc 是静态方法,该方法会调用 Runtime 类中的 gc 方法。
    其实,java.lang.System.gc 等价于 java.lang.Runtime.getRuntime.gc 的简写,都是调用垃圾回收器。
    方法 gc 的作用是提示 Java 虚拟机进行垃圾回收,该方法由系统自动调用,不需要人为调用。该方法被调用之后,由 Java 虚拟机决定是立即回收还是延迟回收。
    jdk8 System 类的部分源码
public final class System {
   ...
    /**
     * Runs the garbage collector.
     * <p>
     * Calling the <code>gc</code> method suggests that the Java Virtual
     * Machine expend effort toward recycling unused objects in order to
     * make the memory they currently occupy available for quick reuse.
     * When control returns from the method call, the Java Virtual
     * Machine has made a best effort to reclaim space from all discarded
     * objects.
     * <p>
     * The call <code>System.gc()</code> is effectively equivalent to the
     * call:
     * <blockquote><pre>
     * Runtime.getRuntime().gc()
     * </pre></blockquote>
     *
     * @see     java.lang.Runtime#gc()
     */
    public static void gc() {
        Runtime.getRuntime().gc();
    }
}
  1. finalize
    与垃圾回收有关的另一个方法是 finalize 方法。该方法在 Object 类中被定义,在释放对象占用的内存之前会调用该方法。该方法的默认实现不做任何事,如果必要,子类应该重写该方法,一般建议在该方法中释放对象持有的资源。

4. 如何判断对象是否可回收

垃圾回收器在对堆进行回收之前,首先需要确定哪些对象是可回收的。常用的算法有两种,引用计数算法和根搜索算法。

1. 引用计数算法

引用计数算法给每个对象添加引用计数器,用于记录对象被引用的计数,引用计数为 0 的对象即为可回收的对象。

虽然引用计数算法的实现简单,判定效率也很高,但是引用计数算法无法解决对象之间循环引用的情况。如果多个对象之间存在循环引用,则这些对象的引用计数永远不为 0,无法被回收。因此 Java 语言没有使用引用计数算法。

2. 根搜索算法(也称,可达性分析法)

主流的商用程序语言都是使用根搜索算法判断对象是否可回收。根搜索算法的思路是,从若干被称为 GC Roots 的对象开始进行搜索,不能到达的对象即为可回收的对象。

在 Java 中,GC Roots 一般包含下面几种对象:

● 虚拟机栈中引用的对象;

● 本地方法栈中的本地方法引用的对象;

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

● 方法区中的常量引用的对象。

5. 引用的分类

引用计数算法和根搜索算法都需要通过判断引用的方式判断对象是否可回收。

JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。


在 JDK 1.2 之后,Java 将引用分成四种,按照引用强度从高到低的顺序依次是:强引用、软引用、弱引用、虚引用。


● 强引用是指在程序代码中普遍存在的引用。垃圾回收器永远不会回收被强引用关联的对象。(类似于必不可少的生活用品)

● 软引用描述还有用但并非必需的对象。只有在系统将要发生内存溢出异常时,被软引用关联的对象才会被回收。在 JDK 1.2 之后,提供了 SoftReference 类实现软引用。(类似于可有可无的生活用品)

● 弱引用描述非必需的对象,其强度低于软引用。被弱引用关联的对象只能存活到下一次垃圾回收发生之前,当垃圾回收器工作时,被弱引用关联的对象一定会被回收。在 JDK 1.2 之后,提供了 WeakReference 类实现弱引用。(类似于可有可无的生活用品)

● 虚引用是最弱的引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收时收到一个系统通知。在 JDK 1.2 之后,提供了 PhantomReference 类实现虚引用。

6. 对象可以被回收,就代表一定会被回收吗?

即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;

  1. 可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
  2. 被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。

7. 方法 finalize 在哪个类中定义,以及它的默认实现是什么?该方法的作用是什么?

方法 finalize 在 Object 类中被定义,该方法的默认实现不做任何事。在释放对象占用的内存之前会调用该方法,如果必要,子类应该重写该方法,一般建议在该方法中释放对象持有的资源。

8. 判断对象是否可回收,有哪两种算法?Java 使用的是哪一种算法?另一种算法有什么不足之处?

判断对象是否可回收的两种算法是引用计数算法和根搜索算法,Java 使用的是根搜索算法。引用计数算法虽然实现简单,判定效率高,但是缺点是无法解决对象之间循环引用的情况,当存在循环引用时,使用引用计数算法会导致无法堆循环引用的对象进行回收。

9. 新生代和老年代分别适合使用哪种垃圾回收算法?

● 在新生代中,大多数对象的生命周期都很短,因此选用复制算法。

● 在老生代中,对象存活率高,因此选用标记—清除算法或标记—整理算法。

10. 在分配内存空间时,为什么大对象直接在老年代中分配?

将大对象直接在老年代中分配的目的是避免在 Eden 区和 Survivor 区之间出现大量内存复制。

相关文章
|
3月前
|
算法 Java
50道java集合面试题
50道 java 集合面试题
|
6月前
|
缓存 Java 关系型数据库
2025 年最新华为 Java 面试题及答案,全方位打造面试宝典
Java面试高频考点与实践指南(150字摘要) 本文系统梳理了Java面试核心考点,包括Java基础(数据类型、面向对象特性、常用类使用)、并发编程(线程机制、锁原理、并发容器)、JVM(内存模型、GC算法、类加载机制)、Spring框架(IoC/AOP、Bean生命周期、事务管理)、数据库(MySQL引擎、事务隔离、索引优化)及分布式(CAP理论、ID生成、Redis缓存)。同时提供华为级实战代码,涵盖Spring Cloud Alibaba微服务、Sentinel限流、Seata分布式事务,以及完整的D
363 1
|
5月前
|
缓存 Java API
Java 面试实操指南与最新技术结合的实战攻略
本指南涵盖Java 17+新特性、Spring Boot 3微服务、响应式编程、容器化部署与数据缓存实操,结合代码案例解析高频面试技术点,助你掌握最新Java技术栈,提升实战能力,轻松应对Java中高级岗位面试。
477 0
|
5月前
|
Java 数据库连接 数据库
Java 相关知识点总结含基础语法进阶技巧及面试重点知识
本文全面总结了Java核心知识点,涵盖基础语法、面向对象、集合框架、并发编程、网络编程及主流框架如Spring生态、MyBatis等,结合JVM原理与性能优化技巧,并通过一个学生信息管理系统的实战案例,帮助你快速掌握Java开发技能,适合Java学习与面试准备。
252 2
Java 相关知识点总结含基础语法进阶技巧及面试重点知识
|
3月前
|
存储 小程序 Java
热门小程序源码合集:微信抖音小程序源码支持PHP/Java/uni-app完整项目实践指南
小程序已成为企业获客与开发者创业的重要载体。本文详解PHP、Java、uni-app三大技术栈在电商、工具、服务类小程序中的源码应用,提供从开发到部署的全流程指南,并分享选型避坑与商业化落地策略,助力开发者高效构建稳定可扩展项目。
|
3月前
|
算法 Java
50道java基础面试题
50道java基础面试题
|
6月前
|
算法 架构师 Java
Java 开发岗及 java 架构师百度校招历年经典面试题汇总
以下是百度校招Java岗位面试题精选摘要(150字): Java开发岗重点关注集合类、并发和系统设计。HashMap线程安全可通过Collections.synchronizedMap()或ConcurrentHashMap实现,后者采用分段锁提升并发性能。负载均衡算法包括轮询、加权轮询和最少连接数,一致性哈希可均匀分布请求。Redis持久化有RDB(快照恢复快)和AOF(日志更安全)两种方式。架构师岗涉及JMM内存模型、happens-before原则和无锁数据结构(基于CAS)。
180 5
|
6月前
|
安全 Java API
2025 年 Java 校招面试常见问题及详细答案汇总
本资料涵盖Java校招常见面试题,包括Java基础、并发编程、JVM、Spring框架、分布式与微服务等核心知识点,并提供详细解析与实操代码,助力2025校招备战。
312 1
|
5月前
|
缓存 Java 关系型数据库
Java 面试经验总结与最新 BAT 面试资料整理含核心考点的 Java 面试经验及最新 BAT 面试资料
本文汇总了Java面试经验与BAT等大厂常见面试考点,涵盖心态准备、简历优化、面试技巧及Java基础、多线程、JVM、数据库、框架等核心技术点,并附实际代码示例,助力高效备战Java面试。
196 0