面试题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):由用户自定义,适用于特殊需求,如加载加密的类文件、从网络加载类等。

类加载器之间的关系:

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

参考资料

相关推荐
神明不懂浪漫10 分钟前
【第五章】Java中的继承与多态
java·开发语言
AI多Agent协作实战派2 小时前
AI多Agent协作系统实战(十七):凌晨4点,我的AI系统在“假装工作“——3个bug同时爆炸的5小时
java·前端·bug
gaolei_eit2 小时前
Java+Ai+vue
java·spring·maven
qq_2518364572 小时前
基于java Web 动漫视频网站毕业论文
java·开发语言·前端
LayZhangStrive3 小时前
JUC相关的函数、注解、变量杂记
java·面试·多线程·juc
未秃头的程序猿3 小时前
给公司做了个AI客服Agent,用的Spring AI 1.0,3天上线领导拍板了
java·后端·ai编程
曹牧3 小时前
Eclipse 批量文本替换
java·ide·eclipse
程序员清风3 小时前
OpenAI官方发布最新提示词技巧!
java·后端·面试
一只枫林3 小时前
MySQL内、外连接知识点汇总
java·前端·数据库
梅头脑3 小时前
写了3年CRUD,volatile和synchronized的区别还是答不清——直到我画出了这张三性两锁边界图
java