Java 热门面试题200道之JVM(7 题)

(1)JVM 由哪些部分组成?

JVM的主要组成部分包括加载器子系统(ClassLoader)、运行时数据区(Runtime Data Area)、执行引擎(Execution Engine)以及本地方法接口(Native Interface, JNI)。

Java虚拟机(JVM)是一个运行Java程序的虚拟环境,它负责将Java字节码转换为机器码并执行。

理解记忆上述几个组成部分:

  • 首先需要准备编译好的Java字节码文件(即class文件)。
  • 然后需要先通过一定方式(类加载器)将class文件加载到内存中(运行时数据区)。
  • 又因为字节码文件时是VM定义的一套指令集规范,底层操作系统无法直接执行,因为需要特定的命令解释器(执行引擎)将字节码翻译成特定的操作系统指令集交给CPU去执行。
  • 这个过程会需要调用到一些不同语言为Java提供的接口(例如驱动、地图制作等),这就用到了本地方法接口(NativeInterface)。

(2)JVM 垃圾回收调优的主要目标是什么?

Java虚拟机(JVM)的垃圾回收(GC)是自动内存管理的核心机制,它负责回收不再使用的对象以释放内存空间。虽然GC是自动进行的,但不当的配置可能导致应用程序性能下降。

主要目标

1. 降低GC停顿时间

GC停顿时间(Stop-The-World, STW)是指JVM在执行某些GC操作时需要暂停所有应用线程的时间。调优的主要目标之一是减少这种停顿:

  • 对于延迟敏感型应用(如交易系统、实时系统),需要最小化最大停顿时间
  • 可以通过选择适当的GC算法(如G1、ZGC)来减少停顿
  • 调整堆大小和区域大小可以影响停顿时间

2. 提高吞吐量

吞吐量是指应用程序执行时间占总运行时间(应用执行时间+GC时间)的比例:

  • 目标通常是达到95%以上的吞吐量
  • 对于批处理系统,高吞吐量比低延迟更重要
  • 可以通过增大堆大小、调整GC线程数来提高吞吐量

3. 优化内存占用

合理的内存占用目标包括:

  • 避免堆过大导致系统资源浪费
  • 避免堆过小导致频繁GC
  • 平衡不同内存区域(新生代/老年代)的比例
  • 监控和优化非堆内存(元空间、线程栈等)

4. 避免内存泄漏和OOM

调优目标包括:

  • 确保不再使用的对象能被及时回收
  • 识别和修复因静态集合、缓存、监听器等导致的内存泄漏
  • 合理设置堆大小以避免OutOfMemoryError

5. 选择合适的GC算法

根据应用特点选择GC算法:

  • Serial GC:小型应用,单处理器
  • Parallel GC:吞吐量优先
  • CMS:低延迟,已废弃
  • G1:平衡吞吐量和延迟
  • ZGC/Shenandoah:超大堆,极低延迟

调优平衡艺术

GC调优往往需要在多个目标之间取得平衡:

  • 低延迟 vs 高吞吐量
  • 小堆(快速GC) vs 大堆(减少GC频率)
  • 预测性 vs 适应性

(3)如何对 Java 的垃圾回收进行调优?

1. 确定调优目标

根据应用类型确定调优重点:

  • Web应用:通常更关注低延迟
  • 批处理应用:通常更关注高吞吐量
  • 混合型应用:需要平衡两者

2 收集GC日志

启用GC日志是调优的第一步,添加以下JVM参数:

复制代码
-XX:+PrintGCDetails 
-XX:+PrintGCDateStamps 
-XX:+PrintGCTimeStamps 
-Xloggc:<gc-log-file-path>

3 分析GC日志

使用工具如GCViewer、GCEasy等分析GC日志,关注:

  • Full GC频率
  • Young GC持续时间
  • 内存回收效率

4 调整堆大小

推荐设置:

  • Xms和Xmx设置为相同值,避免堆动态调整
  • 新生代通常占堆的1/3到1/2

5 选择垃圾回收器

根据应用特点选择合适的回收器:

  • 吞吐量优先:-XX:+UseParallelGC
  • 低延迟优先:-XX:+UseG1GC 或 -XX:+UseZGC(Java 11+)
  • 大内存应用:-XX:+UseG1GC

6 优化GC参数

常见调优参数示例:

复制代码
// G1 GC调优示例
-XX:+UseG1GC 
-XX:MaxGCPauseMillis=200 
-XX:InitiatingHeapOccupancyPercent=45
-XX:ConcGCThreads=4

// Parallel GC调优示例
-XX:+UseParallelGC
-XX:ParallelGCThreads=8
-XX:MaxGCPauseMillis=500

(4)常用的 JVM 配置参数有哪些?

内存相关参数

1. 堆内存设置

  • -Xms:初始堆大小,如 -Xms512m 设置初始堆为512MB
  • -Xmx:最大堆大小,如 -Xmx2g 设置最大堆为2GB
  • -Xmn:新生代大小,如 -Xmn256m
  • -XX:NewRatio:老年代与新生代的比例,如 -XX:NewRatio=2 表示老年代是新生代的2倍
  • -XX:SurvivorRatio:Eden区与Survivor区的比例,如 -XX:SurvivorRatio=8 表示Eden是单个Survivor的8倍

2. 元空间设置

  • -XX:MetaspaceSize:初始元空间大小
  • -XX:MaxMetaspaceSize:最大元空间大小

垃圾回收相关参数

1. 垃圾回收器选择

  • -XX:+UseSerialGC:使用串行垃圾回收器
  • -XX:+UseParallelGC:使用并行垃圾回收器
  • -XX:+UseConcMarkSweepGC:使用CMS回收器
  • -XX:+UseG1GC:使用G1回收器
  • -XX:+UseZGC:使用ZGC(Java 11+)
  • -XX:+UseShenandoahGC:使用ShenandoahGC

2. GC日志参数

  • -XX:+PrintGCDetails:打印GC详细信息
  • -XX:+PrintGCDateStamps:打印GC时间戳
  • -Xloggc:<file>:将GC日志输出到文件
  • -XX:+UseGCLogFileRotation:启用GC日志轮转
  • -XX:NumberOfGCLogFiles=5:保留的GC日志文件数
  • -XX:GCLogFileSize=20M:单个GC日志文件大小

性能调优参数

  • -server:启用服务器模式(64位JVM默认)
  • -client:启用客户端模式(32位JVM默认)
  • -XX:+TieredCompilation:启用分层编译(Java 7+默认)
  • -XX:CompileThreshold:方法调用多少次后编译为本地代码
  • -XX:+UseFastAccessorMethods:优化getter/setter方法

故障排查参数
1. 内存溢出处理

-XX:+HeapDumpOnOutOfMemoryError:内存溢出时生成堆转储文件

-XX:HeapDumpPath= :指定堆转储文件路径
-XX:OnOutOfMemoryError=:内存溢出时执行指定命令
2. 调试参数
-XX:+PrintFlagsFinal:打印所有JVM参数最终值
-XX:+PrintCommandLineFlags:打印命令行设置的JVM参数
-XX:+TraceClassLoading:跟踪类加载过程
-XX:+PrintCompilation:打印方法编译信息

常用参数组合示例

1. Web应用典型配置

复制代码
-Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m 
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+ParallelRefProcEnabled 
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump.hprof

2. 微服务典型配置

复制代码
-Xms512m -Xmx512m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m
-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:G1HeapRegionSize=8m
-XX:+HeapDumpOnOutOfMemoryError

参数选择建议

  1. 根据应用类型选择合适的内存配置
  2. 根据硬件资源设置合理的堆大小
  3. 根据性能目标选择合适的GC算法
  4. 生产环境务必配置OOM时的堆转储
  5. 监控GC日志并根据实际情况调整参数

(5)JVM 的内存区域是如何划分的?

Java虚拟机运行时数据区分为方法区、堆、虚拟机栈、本地方法栈、程序计数器。

1)方法区(Method Area):

  • 存储类信息、常量、静态变量和即时编译器(JIT)编译后的代码。
  • 属于线程共享区域,所有线程共享方法区内存。
  • 在JDK8之前,HotSpot使用永久代(PermGen)来实现方法区,JDK8之后被元空间(Metaspace)取代,元空间使用的是本地内存(Native Memory)。

2)堆 (Heap):

用于存放所有线程共享的对象和数组,是垃圾回收的主要区域。

3)虚拟机栈 (JVM Stack) :

  • 每个线程创建一个栈,用来保存局部变量、操作数栈、动态链接、方法出口信息等。
  • 局部变量表中存储的是基本数据类型(如int、float)以及对象引用。
  • 栈是线程私有的,生命周期与线程相同。

4) 本地方法栈(Native Method Stack):

  • 为本地方法服务,使用JNI(JavaNativeInterface)调用的本地代码在此区域分配内存。
  • 和虚拟机栈类似,也是线程私有的。

5) 程序计数器(Program Counter Register):

  • 是一个小的内存区域,保存当前线程执行的字节码指令的地址或行号。
  • 每个线程都有一个独立的程序计数器,属于线程私有。

还有一个 直接内存(Direct Memory) 这里也提一下,它属于JVM之外的内存区域:

  • 由NIO 库通过ByteBuffer直接分配的内存。
  • 直接内存的大小不受堆内存限制,但会受到本机内存的限制。

(6)JVM 有哪几种情况会产生 OOM(内存溢出)?

Java中常见的OOM情况可以概括为以下几种:

OOM错误类型 原因 常见解决办法
Java Heap Space 堆内存不足,常见于大量对象创建 优化对象使用或增大堆内存
StackOverflowError 栈空间耗尽,常见于递归或深层嵌套调用 优化递归逻辑或增大栈空间
PermGen Space / Metaspace 方法区或元空间不足,常见于频繁动态生成类的场景 增大方法区或元空间,减少类加载频率
Direct Buffer Memory 直接内存不足,常见于 NIO 操作 增大直接内存限制或减少直接内存使用
Unable to Create New Native Thread 线程数过多,超出系统资源限制 控制线程数或线程池大小,避免无限制创建新线程
GC Overhead Limit Exceeded GC 时间过长且回收内存不足 增大堆内存、优化内存使用、调整 GC 策略

StackOverflowError虽然不算OutOfMemoryError但是一般可以归类一起说。

(7)怎么分析 JVM 当前的内存占用情况?OOM 后怎么分析?

分析 JVM 当前的内存占用情况,你可以使用以下方法和工具:

1. 实时监控工具:

  • 使用 jvisualvm 或 jconsole 等工具可以实时监控 Java 应用的内存使用情况。这些工具提供了可视化界面,可以监控内存使用、查看线程、分析堆等。

2. JVM 命令行工具:

  • jstat:用于监视 JVM 内存和垃圾回收情况,可以显示各个堆区的使用情况、GC 时间等。
  • jmap:可以用来生成堆转储文件(Heap Dump),以便进行后续分析。

3. 分析工具:

  • Eclipse Memory Analyzer (MAT):一个强大的 Java 堆分析工具,可以帮助识别内存泄漏和查看内存消耗情况。
  • VisualVM:除了监控功能外,也支持加载和分析 Heap Dump 文件。

当 JVM 发生 OOM(OutOfMemoryError)后,分析步骤通常包括:

1. 获取 Heap Dump 文件:

确保 JVM 在遇到 OOM 时自动生成堆转储文件,可以通过设置 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/heapdump.hprof 参数来实现。

2. 使用分析工具分析 Heap Dump 文件:

  • 使用 MAT 或 VisualVM 打开 Heap Dump 文件,关注以下几点:
    • 查找内存中对象的分布,特别是占用内存最多的对象。
    • 分析这些对象的引用链,确定是哪部分代码引起的内存泄漏或过度消耗。
    • 检查 ClassLoader,以确认是否有过多的类被加载导致的元空间(Metaspace)OOM。

3. 分析日志和异常信息:

  • OOM 之前的日志可能会提供一些导致内存溢出的操作或业务逻辑的线索。

4. 调整 JVM 参数:

  • 根据分析结果,可能需要调整 JVM 的内存分配参数,如增加堆大小 -Xmx 或调整新生代与老年代的比例。

5. 代码优化:

  • 如果分析发现内存泄漏,需要优化代码,减少内存消耗,修复内存泄漏。

6. 监控和预防:

  • 在解决问题后,继续使用监控工具观察内存使用情况,预防未来的内存问题。

通过上述步骤,你可以有效地分析和解决 JVM 的内存占用问题。在实际操作中,可能需要根据具体情况调整分析方法和工具。

相关推荐
golang学习记2 小时前
从0死磕全栈第十天:nest.js集成prisma完成CRUD
开发语言·javascript·jvm
fengdongnan2 小时前
JVM 类加载器详解
java·jvm·类加载器
安然~~~2 小时前
常见的【垃圾收集算法】
java·jvm
低调小一2 小时前
理解 JVM 的 8 个原子操作与 `volatile` 的语义
java·jvm
七夜zippoe3 小时前
JVM 调优在分布式场景下的特殊策略:从集群 GC 分析到 OOM 排查实战(二)
jvm·分布式
Familyism3 小时前
Java虚拟机——JVM
java·开发语言·jvm
^辞安3 小时前
什么是Mvcc
java·数据库·mysql
烈风3 小时前
009 Rust函数
java·开发语言·rust
这次选左边3 小时前
Flutter混合Android开发Release 打包失败GeneratedPluginRegistrant.java,Plugin不存在
android·java·flutter