当前位置: 首页 > news >正文

JVM面试精选 20 题(终)

在这里插入图片描述

目录

      • 1. Java 虚拟机是如何判断一个类是否可以卸载的?
      • 2. Java 虚拟机栈中存储了什么?栈帧又是什么?
      • 3. 什么叫 JVM 的内存泄漏(Memory Leak)?
      • 4. 什么是偏向锁、轻量级锁和重量级锁?
      • 5. 为什么说 ConcurrentHashMap 线程安全,并且效率高?
      • 6. JVM 怎么实现方法重载(Overload)和方法重写(Override)?
      • 7. 什么是逃逸分析的标量替换?
      • 8. JVM 为什么需要常量池?
      • 9. 什么是 JNI(Java Native Interface)?
      • 10. JVM 的内存模型(JMM)是什么?它解决了什么问题?
      • 11. 什么是 GC Roots?哪些对象可以作为 GC Roots?
      • 12. 什么是 JVM 的“元空间”(Metaspace)?
      • 13. 说说什么是 JVM 内存模型中的“主内存”和“工作内存”?
      • 14. 什么是指令重排?为什么会发生?
      • 15. 什么是对象头中的 Mark Word?
      • 16. 什么是 Java 中的类初始化 `<clinit>()` 方法?
      • 17. 说说 Java 中类的加载器有哪些?
      • 18. 什么是 JVM 的内存屏障(Memory Barrier)?
      • 19. 说说 JVM 的即时编译模式有哪些?
      • 20. 简述 JVM 的调优思路和步骤。


1. Java 虚拟机是如何判断一个类是否可以卸载的?

解答:

判断一个类是否可以卸载,需要满足以下三个条件:

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

只有同时满足这三个条件,JVM 才会在 GC 时考虑卸载这个类。

2. Java 虚拟机栈中存储了什么?栈帧又是什么?

解答:

Java 虚拟机栈是线程私有的,它的生命周期与线程相同。它存储了用于方法执行过程中的数据,包括:

  • 局部变量表(Local Variable Table):存放方法参数和方法内部定义的局部变量。
  • 操作数栈(Operand Stack):用于存放方法执行时的中间计算结果。
  • 动态链接(Dynamic Linking):指向运行时常量池中该栈帧所属方法的引用。
  • 方法返回地址(Return Address):当方法执行完毕后,记录返回到哪里继续执行。

栈帧(Stack Frame) 是虚拟机栈的元素,它是用于支持虚拟机进行方法调用和方法执行的数据结构。每个方法从调用到执行结束,都对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

3. 什么叫 JVM 的内存泄漏(Memory Leak)?

解答:

内存泄漏是指程序在申请内存后,无法释放已申请的内存空间,导致系统可用的内存越来越少。虽然 JVM 有自动垃圾回收机制,但仍然可能发生内存泄漏。

常见的内存泄漏场景:

  • 长生命周期的对象持有短生命周期的对象引用:例如,一个静态 HashMap 缓存了很多对象,但这些对象使用完后没有从 HashMap 中移除,导致它们永远无法被回收。
  • 资源未关闭:如数据库连接、网络连接、文件流等,使用后没有调用 close() 方法释放资源,导致内存泄漏。
  • 内部类持有外部类的引用:非静态内部类会隐式持有外部类的引用,如果内部类的生命周期长于外部类,可能导致外部类无法被回收。

4. 什么是偏向锁、轻量级锁和重量级锁?

解答:

这是 Java 对象锁在 JVM 中的不同状态,它们是 JVM 为了提高锁的性能而引入的优化机制。

  • 偏向锁(Biased Locking):当一个线程第一次获得锁时,JVM 会在对象头中记录该线程 ID。如果该线程再次进入同步块,则无需任何同步操作,直接执行。当其他线程竞争时,偏向锁会升级为轻量级锁。
  • 轻量级锁(Lightweight Locking):当偏向锁升级后,或者多个线程交替访问同步块时,JVM 会使用轻量级锁。它通过自旋(Spinning)来等待锁释放,避免了线程的挂起和恢复,开销较小。
  • 重量级锁(Heavyweight Locking):当多个线程同时竞争锁,且自旋无法获取锁时,轻量级锁会膨胀为重量级锁。此时,没有获取到锁的线程会被阻塞wait),进入等待队列,直到锁被释放。

5. 为什么说 ConcurrentHashMap 线程安全,并且效率高?

解答:

ConcurrentHashMap 相比 HashtablesynchronizedMap,效率高的原因在于它采用了更细粒度的锁机制:

  • JDK 1.7:使用 分段锁(Segment Lock)。它将整个哈希表分为多个 Segment,每个 Segment 都是一个独立的锁。当一个线程修改某个 Segment 中的数据时,其他线程可以同时访问或修改其他 Segment,实现了并发。
  • JDK 1.8:放弃了分段锁,而是采用 CAS(Compare-and-Swap)synchronized 结合的方式。put 操作时,只对需要修改的哈希桶加锁,粒度更小。大多数并发操作(如 get)甚至不需要加锁,通过 volatile 保证可见性。

6. JVM 怎么实现方法重载(Overload)和方法重写(Override)?

解答:

  • 方法重载(Overload)

    • 实现:发生在编译期,通过**方法签名(Method Signature)**来区分,即方法名相同,但参数列表(参数类型、个数和顺序)不同。
    • 原理:编译器会根据传入的参数类型、个数等信息,在编译时就确定调用哪个重载方法。
  • 方法重写(Override)

    • 实现:发生在运行期,子类对父类的方法进行重新实现,要求方法名、参数列表和返回值类型都相同。
    • 原理:利用 多态(Polymorphism)。在运行时,JVM 根据对象的实际类型(而不是声明的类型),调用对应的方法。这是通过 虚方法表(Virtual Method Table) 实现的。

7. 什么是逃逸分析的标量替换?

解答:

标量(Scalar) 指一个无法再分解成更小的数据的数据,如 intlong 等基本类型。聚合量(Aggregate) 则可以继续分解,如对象。

标量替换是指当逃逸分析证明一个对象不会被外部访问,并且这个对象可以被分解时,JVM 不再创建这个对象本身,而是直接创建它的成员变量。

举例

public void test() {Point point = new Point(1, 2); // Point 是聚合量System.out.println("x=" + point.x + ", y=" + point.y);
}

如果经过逃逸分析,JVM 发现 point 对象只在 test 方法内部使用,并且可以分解,它可能会直接将 point.xpoint.y 替换为两个独立的局部变量,从而消除对象创建的开销,减轻了 GC 压力。


8. JVM 为什么需要常量池?

解答:

常量池(Constant Pool)class 文件中的一部分,它存储了编译期生成的各种字面量和符号引用。常量池的存在有以下重要作用:

  1. 节省空间:例如,程序中多次出现的字符串常量,只会在常量池中存储一份。
  2. 动态链接:在类加载的解析阶段,会将常量池中的符号引用转换为直接引用。
  3. 支持字面量和符号引用
    • 字面量:如文本字符串、final 常量值等。
    • 符号引用:如类和接口的全限定名、字段的名称和描述符、方法的名称和描述符等。

9. 什么是 JNI(Java Native Interface)?

解答:

JNI(Java Native Interface)是 Java 本地接口,它允许 Java 代码与其他语言(主要是 C/C++)编写的代码进行交互。

使用场景:

  • 调用底层操作系统或硬件接口:Java 本身无法直接访问操作系统底层功能,通过 JNI 可以调用 C/C++ 实现的本地库。
  • 性能敏感的代码:对于某些计算密集型的任务,如果 Java 语言无法达到理想的性能,可以将其核心逻辑用 C/C++ 实现,然后通过 JNI 调用。
  • 利用现有 C/C++ 库:重用已有的 C/C++ 代码库,无需重新用 Java 实现。

10. JVM 的内存模型(JMM)是什么?它解决了什么问题?

解答:

JMM(Java Memory Model)是 Java 虚拟机规范中的一部分,它定义了线程和主内存之间的关系,以及程序中各种变量的访问规则。JMM 解决了并发编程中可见性、原子性和有序性的问题。

  • 可见性:一个线程对共享变量的修改,对其他线程是立即可见的。volatilesynchronized 关键字可以保证可见性。
  • 原子性:一个或多个操作,要么全部执行成功,要么全部不执行。synchronized 可以保证原子性。
  • 有序性:程序执行的顺序和代码的编写顺序一致。JMM 允许编译器和处理器进行指令重排,以提高性能。volatilesynchronized 可以禁止指令重排。

JMM 旨在屏蔽底层硬件和操作系统的差异,确保 Java 程序在各种平台上都能得到一致的内存访问效果。


11. 什么是 GC Roots?哪些对象可以作为 GC Roots?

解答:

GC Roots 是垃圾回收器进行可达性分析的起始点。从这些根节点开始,GC 遍历所有引用链,任何无法被根节点直接或间接访问到的对象,都被认为是可回收的。

可以作为 GC Roots 的对象包括:

  • 虚拟机栈中引用的对象:栈帧中的局部变量表所引用的对象。
  • 方法区中静态属性引用的对象:类中的静态变量。
  • 方法区中常量引用的对象:字符串常量池中的引用。
  • 本地方法栈中 JNI 引用的对象native 方法引用的对象。
  • 所有被同步锁(synchronized)持有的对象

12. 什么是 JVM 的“元空间”(Metaspace)?

解答:

元空间(Metaspace) 是 JDK 1.8 中用来取代永久代(Permanent Generation) 的内存区域。

  • 永久代:在 JDK 1.7 及之前,用于存储类元数据(如类信息、常量池等),它位于 JVM 的堆内存中,受 -XX:MaxPermSize 限制,容易发生 OOM。
  • 元空间:在 JDK 1.8 之后,元数据被移出堆,存放在本地内存(Native Memory) 中。理论上,只要服务器物理内存足够,元空间的大小就不受限制。这降低了 OOM 的风险,但也可能导致操作系统内存耗尽。

13. 说说什么是 JVM 内存模型中的“主内存”和“工作内存”?

解答:

这是 JMM 的一个抽象概念。

  • 主内存(Main Memory):所有线程共享的内存,存储了所有的共享变量。对应于计算机中的物理内存
  • 工作内存(Working Memory):每个线程私有的内存,存储了该线程操作的共享变量的副本。对应于计算机中的高速缓存(Cache)

线程对共享变量的操作(读取和修改)都必须在工作内存中进行,不能直接操作主内存。线程之间也无法直接访问彼此的工作内存,线程间通信必须通过主内存。

14. 什么是指令重排?为什么会发生?

解答:

指令重排是指编译器或处理器为了优化程序执行效率,对源代码中的指令执行顺序进行调整,但不改变单线程下的执行结果

为什么会发生?

  • 编译器优化:在不改变结果的前提下,编译器可以调整指令顺序以提高效率。
  • 处理器优化:为了充分利用 CPU 的乱序执行能力,处理器会调整指令顺序。

指令重排可能带来的问题
在并发场景下,如果一个线程修改了共享变量,而另一个线程读取该变量,由于指令重排的存在,可能会导致意想不到的错误。volatile 关键字可以禁止指令重排,保证有序性。


15. 什么是对象头中的 Mark Word?

解答:

Mark Word 是 Java 对象头的一部分,它存储了对象自身的运行时数据。在 64 位 JVM 中,它通常是 64 位(8 字节),包括:

  • 哈希码(HashCode):当对象调用 hashCode() 方法时,会存储哈希值。
  • GC 年龄(Age):对象在新生代经历的 GC 次数。
  • 锁状态标志位:标记该对象所处的锁状态,如无锁、偏向锁、轻量级锁、重量级锁。
  • 偏向线程 ID:如果处于偏向锁状态,会存储获取锁的线程 ID。

16. 什么是 Java 中的类初始化 <clinit>() 方法?

解答:

<clinit>() 方法是 JVM 编译器自动生成的一个特殊方法,用于执行类初始化。它包括以下内容:

  • 所有静态变量的赋值语句
  • 所有静态代码块中的语句

特点:

  • 线程安全:JVM 会保证一个类的 <clinit>() 方法在多线程环境下被正确地加锁和同步,确保只执行一次。
  • 子类初始化:在初始化一个子类之前,会先初始化它的父类。
  • 惰性加载<clinit>() 方法只在第一次主动使用该类时才会执行。

17. 说说 Java 中类的加载器有哪些?

解答:

Java 默认提供了三层类加载器:

  1. 启动类加载器(Bootstrap ClassLoader)

    • 负责加载 $JAVA_HOME/jre/lib 下的 JDK 核心类库,如 rt.jar
    • 它不是用 Java 实现的,而是用 C++ 实现,无法被 Java 代码直接引用。
  2. 扩展类加载器(Extension ClassLoader)

    • 负责加载 $JAVA_HOME/jre/lib/ext 目录下的扩展类库。
    • 它是用 Java 实现的,其父类加载器是启动类加载器。
  3. 应用程序类加载器(Application ClassLoader)

    • 负责加载用户类路径(CLASSPATH)上的类库。
    • 它是我们平时编写的 Java 程序的默认加载器。其父类加载器是扩展类加载器。

18. 什么是 JVM 的内存屏障(Memory Barrier)?

解答:

内存屏障是 JVM 插入在指令序列中的特殊指令,用于禁止处理器对指令进行重排序,以保证内存操作的可见性和有序性。

  • 写屏障(Store Barrier):强制将处理器缓存中的数据写回主内存,保证其他线程的可见性。
  • 读屏障(Load Barrier):强制从主内存中读取最新的数据,而不是从处理器缓存中读取。

volatile 关键字就是通过插入内存屏障来保证变量的可见性和有序性。


19. 说说 JVM 的即时编译模式有哪些?

解答:

HotSpot JVM 支持三种即时编译模式:

  1. 解释模式(-Xint):JVM 纯粹使用解释器执行字节码,不进行 JIT 编译。这种模式启动快,但执行效率低。
  2. 编译模式(-Xcomp):JVM 优先使用 JIT 编译器,将所有代码编译为本地代码后执行。这种模式启动慢,但执行效率高。
  3. 混合模式(-Xmixed):JVM 默认的模式。程序启动时使用解释器执行,快速启动;同时,JVM 会监控热点代码,并由 JIT 编译器将其编译成高效的本地代码,以达到最佳性能。

20. 简述 JVM 的调优思路和步骤。

解答:

JVM 调优是一个复杂的过程,一般可以遵循以下步骤:

  1. 监控和分析:使用 JDK 自带工具(如 JConsole、VisualVM) 或其他 APM 工具,监控 JVM 的各项指标,如 CPU、内存、GC 频率和耗时、线程状态等。
  2. 确定性能瓶颈:通过监控数据,找出主要问题。通常是 GC 频繁、GC 耗时长、内存泄漏或 CPU 使用率过高等。
  3. 制定调优策略
    • 内存问题:如果 GC 频繁,可能是堆内存设置过小;如果 Full GC 耗时过长,可能是老年代过大或 GC 算法不合适。
    • CPU 问题:可能是线程死锁或热点代码效率低下。
  4. 调整 JVM 参数:根据策略,调整 -Xmx-Xms-XX:NewRatio 等参数,或更换垃圾回收器(如从 CMS 切换到 G1)。
  5. 反复测试和验证:每次只修改一个参数,然后进行压力测试,验证调优效果。重复这个过程,直到达到预期的性能目标。

核心思想:先找出问题,再对症下药,每次只做最小化改动,并进行验证。


希望这 20 道 JVM 面试题的详细解析能帮助你对 JVM 有更深入的理解,并在面试中脱颖而出!

http://www.dtcms.com/a/341676.html

相关文章:

  • 数据结构之排序大全(2)
  • 【科研绘图系列】R语言绘制平滑曲线折线图
  • 2025招商铸盾车联网CTF竞赛初赛题解
  • Vue 3 高性能实践 全面提速剖析!
  • 基于SpringBoot+Vue的吴韵苏香文旅小程序(协同过滤算法、Echarts图形化分析、腾讯地图API、二维码识别)
  • Linux KGDB 内核调试完全指南:原理、架构与应用
  • ADG duplicate实施方案详细教程(单机版)
  • 基于STM32单片机智能药盒定时吃药喂水蓝牙APP设计
  • abc Replace
  • cadence16.6修改原理图的Page Number过程中遇到问题
  • 工地智能安全带让高空作业更安全
  • PCB题目基础练习3
  • 前端项目面试分析
  • 解决 nginx: [warn] “ssl_stapling“ ignored, issuer certificate not found 报错
  • cobbler
  • 连续空间强化学习:策略输出的两种形态 —— 概率分布与确定性动作
  • 智慧城市SaaS平台/市政设施运行监测系统之排水管网运行监测、综合管廊运行监测
  • lesson43:Python操作MongoDB数据库完全指南
  • Hyperledger Fabric官方中文教程-改进笔记(十三)-使用测试网络创建通道
  • 25年CATL宁德时代社招晋升竞聘Veirfy测评SHL题库演绎数字语言推理答题指南
  • Js逆向 某花顺登录滑块逆向
  • AI入门学习--理解token
  • Springboot 项目配置多数据源
  • TDengine IDMP 运维指南(5. 使用 Helm 部署)
  • C++ 数据结构 和 STL
  • Python如何将两个列表转化为一个字典
  • Spring Framework 常用注解详解(按所属包分类整理)
  • innovus auto_fix_short.tcl
  • MTK Linux DRM分析(三)- drm_drv.c分析
  • 【智能体记忆】记忆如何塑造我们:深入探究记忆的类型