面试题006-Java-JVM(下)

面试题006-Java-JVM(下)

目录

  • 面试题006-Java-JVM(下)
    • 题目自测
    • 题目答案
      • [1. 为什么需要GC?](#1. 为什么需要GC?)
      • [2. 有哪些常见的GC?](#2. 有哪些常见的GC?)
      • [3. Minor GC 和 Full GC有什么区别?](#3. Minor GC 和 Full GC有什么区别?)
      • [4. 如何判断一个对象是否死亡?](#4. 如何判断一个对象是否死亡?)
      • [5. 讲一下可达性分析算法的流程?](#5. 讲一下可达性分析算法的流程?)
      • [6. 如何判断一个常量是废弃常量? 如何判断一个类是无用的类?](#6. 如何判断一个常量是废弃常量? 如何判断一个类是无用的类?)
      • [7. 垃圾收集有哪些算法,各自的特点?](#7. 垃圾收集有哪些算法,各自的特点?)
      • [8. 默认的垃圾回收器是哪一个?](#8. 默认的垃圾回收器是哪一个?)
      • [9. 说一下G1垃圾收集器的步骤,有什么缺点?](#9. 说一下G1垃圾收集器的步骤,有什么缺点?)
      • [10. 什么是类加载?何时类加载?类加载流程?](#10. 什么是类加载?何时类加载?类加载流程?)
      • [11. 知道哪些类加载器?类加载器之间的关系?](#11. 知道哪些类加载器?类加载器之间的关系?)
    • 参考资料

题目自测

  • 1. 为什么需要GC?
  • 2. 有哪些常见的GC?
  • 3. Minor GC 和 Full GC有什么区别?
  • 4. 如何判断一个对象是否死亡?
  • 5. 讲一下可达性分析算法的流程?
  • 6. 如何判断一个常量是废弃常量? 如何判断一个类是无用的类?
  • 7. 垃圾收集有哪些算法,各自的特点?
  • 8. 默认的垃圾回收器是哪一个?
  • 9. 说一下G1垃圾收集器的步骤,有什么缺点?
  • 10. 什么是类加载?何时类加载?类加载流程?
  • 11. 知道哪些类加载器?类加载器之间的关系?

题目答案

1. 为什么需要GC?

答:垃圾收集器通过自动内存管理,解决了手动管理内存时常见的问题(如内存泄漏和野指针)等,通过自动回收不在使用的对象、整理内存碎片等,极大的提升了程序的稳定性和开发效率。使得程序员可以专注业务逻辑的处理,而不必担心复杂的内存管理等问题。需要GC的主要原因如下

  • 防止内存泄漏:内存泄漏是指程序在运行过程中,未正确释放不再使用的内存,导致系统内存逐渐减少,最终可能导致系统崩溃。
  • 提高开发效率:GC自动化了内存管理,使得程序员可以更加专注业务逻辑的实现,不需要担心复杂的内存管理逻辑,从而提高了代码的开发效率和可维护性。
  • 优化内存使用:GC会定期扫描堆内存,回收不再使用的对象所占的内存,释放系统资源,从而优化内存的使用。
  • 提高程序稳定性:通过自动回收不再使用的对象,GC可以有效地防止内存耗尽,从而提高程序的稳定性和可用性。

2. 有哪些常见的GC?

答:垃圾收集器是实现垃圾回收操作的具体机制。常见的垃圾收集器有:

  • Serial GC:
    • 单线程的垃圾收集器,工作时会暂停所有的应用线程。
    • 适用于单处理器机器或者小型应用程序,堆内存较小的情况下效果较好。
    • 启动参数:-XX:+UseSerialGC
  • Parallel GC:
    • 多线程的垃圾收集器,可以利用多核处理器进行并行垃圾收集,提高吞吐量。JDK8的默认收集器。
    • 适用于多处理器机器和对响应时间要求不高的应用,目标是最大化应用程序的吞吐量。
    • 启动参数:-XX:+UseParallelGC 或者 -XX:+UseParallelOldGC。
  • CMS GC:
    • 低停顿的垃圾收集器,使用并发标记-清除算法,尽量减少垃圾收集导致的应用停顿时间。
    • 用于对响应时间要求高的应用程序,特别是需要大堆内存且停顿时间敏感的服务,如Web服务器。
    • 启动参数:-XX:+UseConcMarkSweepGC。
  • G1 GC:
    • 向服务器应用的垃圾收集器,采用分区算法,将堆划分为多个独立区域,优先回收垃圾最多的区域,能够提供更可预测的停顿时间。JDK9之后的默认收集器。
    • 适用于大堆内存、高吞吐量和低停顿时间要求的应用,是CMS GC的替代者。
    • 启动参数:-XX:+UseG1GC
  • ZGC:
    • 可扩展的低停顿垃圾收集器,设计目标是在拥有非常大的堆(TB级别)时,仍然能保持极低的停顿时间(通常在几毫秒以内)。
    • 适用于需要处理非常大堆内存且对停顿时间要求极高的应用。
    • 启动参数:-XX:+UseZGC

3. Minor GC 和 Full GC有什么区别?

答:Minor GC 和 Full GC是两种不同的垃圾收集操作,他们主要的区别是执行范围和触发条件。

  • Minor GC:
    • 作用范围:Minor GC 主要在新生代进行垃圾回收,新生代包括Eden区和两个Survivor区。
    • 触发条件:当新生代Eden区空间满时,会触发Minor GC。Eden区中的对象会被回收或复制到Survivor区。
    • 系统影响:Minor GC执行速度快,停顿时间短,对系统影响小。
  • Full GC:
    • 作用范围:Full GC 会回收整个堆、包括新生代和老年代,以及方法区或者元空间。
    • 触发条件:老年代空间不足、调用System.gc()方法、永久代/元空间内存不足、特定的JVM行为或调优参数配置也可能触发Full GC。
    • 系统影响:因为要清理整个堆的内存,开销较大,停顿时间长,对系统影响较大,需要避免频繁的Full GC。

4. 如何判断一个对象是否死亡?

答:如果一个对象不被任何变量或对象引用,那么它就是无效对象,需要被垃圾回收器回收。主要依据两种方式:引用计数法和可达性分析算法。

  • 引用计数法:
    • 在对象头维护着一个counter计数器,对象被引用一次计数器就加1,引用失效计数器减1。当计数器为0时就认为该对象死亡了。
    • 这个方法实现简单,效率高。但无法解决循环引用问题,容易导致内存泄漏。
  • 可达性分析算法:
    • 这是Java中实际采用的方法判断对象是否可以被回收。它的基本思想是通过一系列称为"GC Roots"的对象作为根节点,向下搜索。节点走过的所有路径称为引用链,当一个对象到GC Roots没有任何引用链相连的话,则证明该对象不可活,需要被回收。

5. 讲一下可达性分析算法的流程?

答:可达性分析算法是现代JVM中用来判断对象是否存活的主要方法。该算法通过从一组被称为"GC Roots"的根对象出发,沿着这些对象引用链,找到所有可达对象。那些不可达的对象会被认为是垃圾,可以被回收。

  1. 确定 GC Roots:首先需要确定一系列称为"GC Rooos"的对象集合。这些对象包括
    • 虚拟机栈(栈帧中本地变量表)中引用的对象。
    • 本地方法栈(Native方法)中引用的对象。
    • 方法区中类静态属性引用的对象。
    • 方法区中常量引用的对象。
    • 被同步锁持有的对象。
  2. 从根节点开始搜索:从GC Roots开始,JVM会通过深度优先搜索(DFS)或广度优先搜索(BFS),搜索过程中能被根节点引用的对象被认为是可达的,不会被回收。
  3. 标记不可达对象:搜索结束后,没有被搜索到的对象,被认为是不可达的。对这些不可达的对象进行标记。
  4. 二次标记与清理:在此阶段,JVM会进行筛选,判断这个对象是否有必要执行finalize()方法。如果没必要那么对象就会被直接回收。如果有必要执行finalize(),那么对象会被放入一个F-Queue队列中,等待后续处理。finalize()方法执行后,JVM会进行第二次可达性分析,如果对象仍然不可达,那么它将被彻底回收。

6. 如何判断一个常量是废弃常量? 如何判断一个类是无用的类?

答:

  • 判断废弃常量:如果常量池中的常量没有被任何对象和变量引用,就会被判断为废弃常量。如一个字符串"abc"进入了常量池,系统中没有任何的String对象引用这个常量,也没有其他地方引用这个字面量,那么"abc"会被清除出常量池。
  • 判断无用类:判断无用类的条件比较严格,要满足下面三个条件才会被认为是无用类
    • 该类的所有对象都已经被清除,也就是堆中没有任何该对象的信息。
    • 加载该类的ClassLoader已经被回收。
    • 该类的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

7. 垃圾收集有哪些算法,各自的特点?

答:垃圾收集算法用于自动管理和回收不再使用的内存空间。常见的垃圾回收算法有:

  • 标记-清除算法:首先遍历标记出所有不需要回收的对象,然后遍历堆中的对象,将标记的对象清除。
    • 特点:优点是简单,能有效回收无用对象。缺点是标记和清除效率不高,标记清除后会产生大量不连续的内存碎片。
  • 复制算法:将内存分成两块相等的区域,每次只使用其中的一块。当这一块使用完后,将还存活的对象复制到另一块。然后再把使用的空间全部清除。
    • 特点:适合新生代,只复制存活对象,速度快,没有内存碎片。但缺点是内存利用率低,只有一半内存空间被使用。
  • 标记-整理算法:第一阶段与清除标记算法一样。然后再移动存活对象,按照内存地址依次排序,然后清理掉端边界以外的内存。
    • 特点:适合老年代,解决了内存碎片问题。但是因为需要整理对象,所以效率也不高。
  • 分代收集算法:根据对象存活周期的不同,将内存划分为几块。一般Java堆分为新生代和老年代。新生代使用复制算法,老年代使用标记-清除算法、标记-整理算法。

8. 默认的垃圾回收器是哪一个?

答:在Java 8中,默认的垃圾回收器是Parallel GC。Java 9及之后,默认的垃圾回收器说G1 GC。

  • Parallel GC:主要追求高吞吐量,适用于需要大量数据处理、批处理或后台任务的应用。它通过多线程并行处理垃圾回收,能够充分利用多核处理器的能力。

  • G1 GC:设计目的是减少垃圾回收停顿时间,使其更适合需要低停顿时间的应用程序,例如需要较好响应时间的交互式应用。

  • 查看默认的垃圾收集器:

    bash 复制代码
    java -XX:+PrintCommandLineFlags -version

9. 说一下G1垃圾收集器的步骤,有什么缺点?

答:G1 是一款面向服务端应用的垃圾收集器,将内存区域分成多个独立的Region。首先估计每个Region中垃圾的数量,每次从垃圾回收价值最大的Region开始回收,因此可以获得最大的回收效率。

G1 GC的步骤如下:

  • 初始标记:需要暂停应用线程,然后使用一条线程标记GC Roots直接可达的对象。
  • 并发标记:使用一条标记线程和用户线程一起并发执行。在此过程进行可达性分析。
  • 最终标记:暂停应用线程,使用多条标记线程,标记在并发标记阶段发生变动的对象。
  • 筛选回收:计算每个区域垃圾比例,选择回收价值大的区域,将存活对象复制到新区域,并整理旧区域。

G1收集器的缺点:

  • 调优复杂:G1 GC具有许多参数,调优过程复杂,可能需要大量的实验和监控。
  • 额外的内存开销:G1需要维护额外的数据结构来跟踪每个区域的状态,这会增加一定的内存开销。
  • 处理大对象性能较差:大对象会占用多个连续的区域,如果内存中没有足够连续的空闲区域,就可能导致GC频繁发生。

10. 什么是类加载?何时类加载?类加载流程?

答:类加载是JVM将字节码文件(.class)加载的内存中,并对其进行校验、解析、初始化,最终生成Class对象的过程。

类加载遵循"按需加载"的原则,即类在被首次主动使用时,JVM才会加载它。

  • 在遇到 new、putstatic、getstatic、invokestatic 字节码指令时,如果类尚未初始化,则需要先触发其初始化。
  • 对类进行反射调用时,如果类还没有初始化,则需要先触发其初始化。
  • 初始化一个类时,如果其父类还没有初始化,则需要先初始化父类。
  • 虚拟机启动时,需要指定一个包含 main() 方法的主类,虚拟机会先初始化这个主类。
  • 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类还没初始化,则需要先触发其初始化。

类加载的流程分为5个阶段,分别为加载、验证、准备、解析和初始化。

  • 加载:查找并加载类的二进制数据,创建一个代表该类的Class对象。
  • 验证:确保类文件的字节码符合JVM规范,没有安全性和稳定性问题。
  • 准备:为静态成员变量分配内存,并将其默认为初始化值。
  • 解析:将类中的符号引用转换为直接引用。
  • 初始化:执行类的静态初始化块和静态变量的初始化代码。

11. 知道哪些类加载器?类加载器之间的关系?

答:类加载器是负责加载类文件的对象,类加载器将字节码文件读取进内存,并将其转换为Class对象。Java中的类加载器体系具有层次结构,各种类加载器之间存在父子关系,遵循双亲委派模型。

  • 启动类加载器(Bootstrap ClassLoader):最顶层的加载类,由C/C++实现,通常表示为 null,并且没有父级,主要用来加载 JDK 内部的核心类库。
  • 扩展类加载器(Extension ClassLoader):主要负责加载 %JRE_HOME%/lib/ext 目录下的 jar 包和类以及被 java.ext.dirs 系统变量所指定的路径下的所有类
  • 应用程序类加载器(Application ClassLoader):面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。
  • 自定义加载器(Custom ClassLoader):由用户自定义,适用于特殊需求,如加载加密的类文件、从网络加载类等。

类加载器之间的关系:

  • 每个类加载器都有一个父类加载器,除了启动类加载器。
  • 都遵循双亲委派模型。

参考资料

相关推荐
天天扭码15 分钟前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶16 分钟前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺20 分钟前
Spring Boot框架Starter组件整理
java·spring boot·后端
小曲程序28 分钟前
vue3 封装request请求
java·前端·typescript·vue
陈王卜1 小时前
django+boostrap实现发布博客权限控制
java·前端·django
小码的头发丝、1 小时前
Spring Boot 注解
java·spring boot
java亮小白19971 小时前
Spring循环依赖如何解决的?
java·后端·spring
飞滕人生TYF1 小时前
java Queue 详解
java·队列
武子康1 小时前
大数据-230 离线数仓 - ODS层的构建 Hive处理 UDF 与 SerDe 处理 与 当前总结
java·大数据·数据仓库·hive·hadoop·sql·hdfs
武子康1 小时前
大数据-231 离线数仓 - DWS 层、ADS 层的创建 Hive 执行脚本
java·大数据·数据仓库·hive·hadoop·mysql