jvm核心知识点

133 阅读27分钟

jvm内存模型

  1. 方法区(Method Area)、堆(Heap)、栈(Stack)、本地方法栈、程序计数器
  2. 堆内存结构:新生代(Eden,From Survivor,To Survivor)、老年代
  3. 元空间(Metaspace)与永久代(PermGen)的区别

类加载机制

  1. 类加载过程:加载 -> 验证 -> 准备 -> 解析 -> 初始化
  2. 类加载器种类:启动类加载器(Bootstrap)、扩展类加载器(Extension)、应用程序类加载器(AppClassLoader
  3. 双亲委派模型(Parent Delegation Model

垃圾回收机制(GC)

  1. GC Roots 的类型(虚拟机栈中的引用对象、类静态属性引用对象等)
  2. 常见垃圾回收算法:标记-清除、复制、标记-整理、分代收集
  3. 常见垃圾收集器:SerialParallel ScavengeCMSG1ZGC
  4. GC 日志分析(如Xlog:gc*

jvm性能调优

  1. JVM 启动参数配置(堆大小、元空间大小、GC 策略)
  2. 常见 JVM 参数实例:
-Xms512m Xmx1024m -XX:MetaspaceSize=128m -XX:+UseG1GC

3. 性能监控工具:JConsoleVisualVMMATArthasSkyWalking

jvm异常场景排查

  1. 常见异常类型:OOMOut of Memory)、StackOverflowErrorGC overhead limit exceeed
  2. 如何生成和分析 head dump 文件

jvm字节码与执行引擎

  1. Java 字节码结构(Class 文件格式)
  2. Class 文件解析工具:javap -c
  3. 执行引擎:解释执行 vs 即时编译(JIT

常见面试问题汇总

jvm内存结构

  1. JVM 内存分为哪些区域?各有什么作用?

程序计数器Program Counter Register

  • 作用:记录当前线程所执行的字节码指令地址;对于 java 方法,记录的是虚拟机字节码指令的地址,对于 Native 方法,则是空(undefined
  • 特点:线程私有;唯一一个在 JVM 规范中没有规定 OutOfMemoryError 的区域

java虚拟机栈Java Virtual Machine Stack

  • 作用:存储方法调用时的局部变量、操作数栈、动态链接、方法出口等信息;每个方法被执行时会创建一个栈帧(Stack Frame),并压入栈中
  • 特点:线程私有;生命周期与线程相同;可能出现 StackOverflowError(栈溢出) 或 OutOfMemoryError(内内存不足)

本地方法栈Native Method Stack

  • 作用:为 JVM 使用到的 Native 方法服务(如通过 JNI 调用的 C/C++ 方法)
  • 特点:与 Java 虚拟机栈类似,但服务于 Native 方法;具体实现由 JVM 提供商决定(如 HotSpot 中两者合二为一)

java堆Java Heap

  • 作用:存放对象实例(几乎所有对象实例都在这里分配内存);是垃圾回收器(GC)管理的主要区域
  • 特点:线程共享;启动时创建;可通过 -Xms-Xmx 设置初始堆大小和最大堆大小;分为新生代(Young Generation)和老年代(Old Generation),进一步细分为 Eden 区,SurvivorFromTo

方法区Method Area

  • 作用:存储类信息(类的元数据)、常量池、静态变量、编译器编译过后的代码等
  • 特点:线程共享;在 JDK8 及之前,使用永久代(PermGen)实现;在 JDK8 及之后,被元空间(Metaspace)取代

运行时常量池Runtime Constant Pool

  • 作用:是方法区的一部分;存储编译期生成的各种字面量和符号引用(如字符串常量、类与方法的符号引用)
  • 特点:类加载后常量池中的内容会被放入运行时常量;支持动态添加常量(如 String.intern()

元空间Metaspace JDK8+

  • 作用:替代永久代(PermGen),用于存储类的元数据(如类名、方法定义、字段定义等)
  • 特点:使用本地内存(Native Memory),不再受限于 PermGen 固定大小;默认情况下自动调整大小,可通过参数控制(如 -XX:MaxMetaspaceSize);更安全,避免了 PermGen OOM 的问题
区域名称是否线程私有是否可OOM主要作用
程序计数器指向当前线程执行的字节码指令
JAVA虚拟机栈存储方法调用的局部变量、操作数栈
本地方法栈为 Native 方法服务
Java堆存放对象实例,GC主要区域
方法区(JDK8及以前)存储类信息、常量池、静态变量等
运行时常量池方法区的一部分,存方法编译器常量
元空间(JDK8+)替代方法区,存储类元数据
  1. Java 堆和栈的区别是什么?
特性Java堆Java栈
所属线程所有线程共享每个线程独立拥有
存储内容对象实例、数组等方法中的局部变量、基本数据类型、对象引用(Reference)、操作数栈等
生命周期与JVM生命周期一致,程序启动时创建,关闭时销毁与线程生命周期一致,方法调用时压栈,调用结束出栈
访问方式可以被多个线程访问(需注意线程安全)线程私有,不存在并发问题
内存管理由垃圾回收器(GC)自动管理自动分配和释放,无需手动干预
性能开销分配和回收成本较高分配和释放速度快,效率高
异常控制OutOfMemoryError(当对空间不足时)StackOverflowError(递归过深或栈空间不足)OutOfMemoryError(线程过多导致栈无法分配)
大小控制可通过 -Xms 和 -Xmx 设置初始和最大堆大小可通过-Xss设置每个线程栈大小
  1. Metaspace 是什么?和 PermGen 有什么区别?

MetaspaceJVM 用来存储类的元信息的区域(类名、方法信息、字段信息、编译后的代码、常量池等)。

特性PermGen(JDK 7 及以前)Metaspace(JDK 8+)
内存类型堆内存的一部分本地内存(Native Memory
默认最大大小固定大小(受限于 -XX:MaxPermSize默认不限制(可自动扩展)
OOM风险容易因为加载大量类导致OOM 更安全,但仍可能OOM(需设置上限)
垃圾回收机制Full GC 回收无用类元数据同样由 GC 回收,但更灵活
配置参数-XX:PermSize,-XX:MaxPermSize-XX:MetaspaceSize,-XX:MaxMatespaceSize
是否需要手动调优是,常需调整大小否,通常自动管理,但生产环境建议设置上限

Metaspace 优势有哪些?以及潜在的问题?

优势:

  • 避免 PermGen OOM 问题:使用本地内存,理论上不受堆大小的限制
  • 更灵活的内存管理:根据实际需求自动扩展或收缩
  • Native 库更好的兼容性:更贴近底层操作系统资源管理

潜在的问题

  • 仍可能发生 OOM:如果不设置 MaxMetaspaceize,可能导致占用过多的本地内存,最终抛出 java.lang.OutOfMemoryError: Metaspace
  • 类加载泄露风险:如 ClassLoader 没有被正确回收,会导致元数据持续增长

类加载机制

  1. 类加载的过程是怎样的?

加载Loading

  • 作用:查找并加载类的二进制字节流(.class文件),并将其读入内存
  • 触发方式:
    • 显示加载:如 Class.forName("com.example.MyClass")
    • 隐式加载:如遇到 new 关键字、静态字段访问、调用静态方法等
  • 结果:在方法区或元空间中创建一个 java.lang.Class 对象

注意:加载阶段可以使用自定义的 ClassLoader 来实现类的动态加载

验证Verification

  • 作用:确保加载的 .class 文件格式正确,符合当前 JVM 的要求,防止恶意代码破坏虚拟机
  • 验证内容:
    • 文件格式验证(是否符合 Class 文件规范)
    • 元数据验证(语义分析,如继承关系是否合法)
    • 字节码验证(确保操作不会危害虚拟机安全)
    • 符号引用验证(确保常量池中的符号引用能解析)

注意:如果验证失败,会抛出 java.lang.VerifyError 异常

准备Preparation

  • 作用:为类的静态变量(static fields)分配内存,并设置默认初始值(不是程序员赋的值)
  • 实例:
// 准备阶段只是将 value 初始化为0,真正的赋值在初始化阶段
public static int value = 123;

注意:常量(final static)在准备阶段就会被显式赋值

解析Resolution

  • 作用:将类、接口、字段和方法的符号引用(Symbolic Reference)替换成直接引用(Direct Reference
  • 符号引用:用一组符号来描述所引用的目标,与内存无关
  • 直接引用:指向目标在内存中的实际地址
  • 解析时机:
    • 可在类加载时解析(静态解析)
    • 也可以在第一次使用该符号引用时解析(运行时常量池加载)

初始化Initialization

  • 作用:真正执行类中定义的 java 程序代码 (即 <clinit> 方法),包括
    • 执行类构造器 <clinit>(由编译器自动收集所有静态变量的赋值动作和静态代码块合并生成)
    • 执行接口的默认初始化逻辑
  • 触发条件(主动引用):
    • 创建类的实例(如 new MyClass()
    • 调用类的静态方法(如 MyClass.staticMethod()
    • 使用或赋值类的静态非 final 字段
    • 使用反射调用类(如 Class.forName()
    • 子类初始化前会先初始化父类
    • 包含 main 方法的类会被首先初始化

注意:只有主动引用才会触发类的初始化,被动引用(如通过子类引用父类静态字段)不会导致子类初始化

  1. 什么是双亲委派模型?为什么需要它?

在java中,类加载之间存在一种层级结构,当一个类加载器收到类加载请求时,它不会立即自己去加载这个类,而是将请求委托给其父类加载器去处理,只有当父类加载器无法完成加载任务时,才由当前类加载器尝试加载。

  • 避免类的重复加载
    • 同一个类只被加载一次,防止多个类加载器分别加载相同的类,造成内存浪费和冲突
  • 保障核心类的安全性
    • 核心类(如 java.lang.Object)只能由 Bootstrap ClassLoader 加载
    • 即使用户自定义了一个 java.lang.String,也不会被加载,防止恶意篡改核心类
  • 保证类加载的一致性和唯一性
    • 同一个全限定名的类,在整个 JVM 中只会有一个 Class 对象
    • 不同类加载器加载的相同类会被认为是不同的类(如不同模块加载的类隔离)
  • 支持模块化与隔离
    • OSGiTomcat 等框架使用自定义类加载器实现模块隔离,但仍然遵循双亲委派原则来加载公共依赖

优点:安全性高(防止核心类被篡改或替换)、类唯一性(同一个类在整个 JVM 中只有一个实)、资源共享(多个类加载器可以共享已加载的核心类)、可扩展性强(允许自定义类加载器,同时保持统一加载机制)

类加载器层级结构

  • Bootstrap ClassLoader(启动类加载器):最顶层,由 C++ 实现,负责加载 JVM 核心类库(如 rt.jar 中的 java.lang.* 类)
  • Externsion ClassLoader(扩展类加载器):负责加载 $JAVA_HOME/jre/lib/ext 目录下的类或 java.ext.dirs 指定路径中的类
  • Apllication ClassLoader(应用程序类加载器):也叫系统类加载器,负责加载用户类路径(classpath)中的类
  • Custom ClassLoader(自定义类加载器):开发者自定义的类加载器,用于实现特殊需求(如热部署、加密类加载等)

双亲委派模型的工作流程

  • 应用程序调用 ClassLoader.loadClass("com.exmple.MyClass")
  • 当前类加载器首先检查是否已经加载过该类(通过 findLoadedClass(name)
  • 如果未加载,则调用父类加载器的 loadClass() 加载
  • 父类加载器重复上述过程,直到达到 Bootstrap ClassLoader
  • 如果所有父类加载器都无法加载该类,才由当前类加载器尝试加载(调用 findClass()

注意:这个过程是递归的,确保核心类加载器始终由最上级加载器加载

  1. 如何打破双亲委派模型?

虽然双亲委派是一个良好的设计原则,但是在某些场景下也需要“破坏”它,例如:

  • JDBCSPI 机制:JDBC 驱动(如 MySQLOracle)由 Application ClassLoader 加载,但 DriverManager 是由 Bootstrap ClassLoader 加载的。为了解决这个问题,Java 使用了线程上下文类加载器(Thread.currentThread().getContextClassLoader()),绕过了双亲委派机制。

  • OSGi 模块化框架:OSGi 实现了自己的类加载策略,允许同一个类被多个 Bundle(模块)加载,互不影响

  • Tomcat 自定义类加载器:Tomcat 使用自己的 WebAppClassLoader 来加载每个 Web 应用的类,并打破了双亲委派,使得子应用优先加载自己的类,而不是父类加载器的类

打破方式

重写 ClassLoader.loadClass() 方法,不先调用 super.loadClass(),而是直接调用 findClass()

GC 与垃圾回收

  1. Java 中有哪些常用的垃圾回收器?

Serial GC

  • 特点:单线程;使用复制算法(Copying);STWStop the World)期间暂停所有用户线程
  • 适用场景:客户端模式下运行的小型程序(如桌面应用)
  • 启用参数:-XX:+UseSerialGC

Serial Old GC

  • 特点:Serial GC 的老年代版本;使用标记-整理算法(Mark-Compact
  • 适用场景:与 Serial 配合使用,或作为 CMS 后备方案
  • 启用参数:-XX:+UseSerialGC (自动启用 Serial Old

Parallel Scavenge GC

  • 特点:多线程;吞吐量优先(Throughput First);可控制最大停顿时间和吞吐量目标
  • 适用场景:后台服务、批量处理、科学计算等对吞吐量敏感的应用
  • 启用参数:-XX:+UseParallelGC

Parallel Old GC

  • 特点:Parallel Scavenge 的老年代版本
  • 适用场景:多线程 + 标记-整理算法
  • 启用参数:-XX:+UseParallelOldGC

CMS(Concurrent Mark Sweep) GC

  • 特点:
    • 并发收集,低延迟(Latency Sensitive
    • 分为四个阶段:初始标记、并发标记、重新标记、并发清除
    • 存在内存碎片问题
  • 缺点:内存占用较高;并发时失败会退化为 Serial Old,导致长时间 STW
  • 适用场景:Web 服务、实时交易系统等要求响应快的场景
  • 启用参数:-XX:+UseConcMarkSweepGCJDK 8 及以前可用)

G1(Garbage First) GC

  • 特点:面向服务端应用设计;将堆划分为多个大小相等的 region;可预测的时间停顿模型(Pause Prediction Model);支持并发和并行收集;自动压缩内存(避免碎片)
  • 优点:高吞吐 + 低延迟兼顾;适合大堆内存(如几 GB 到几十 GB
  • 适用场景:大数据,微服务,分布式系统
  • 启用参数:-XX:+UseG1GCJDK 7+JDK 9 默认)

ZGC(Z Garbage Collector)

  • 特点:超低延迟(<10ms);支持TB级别堆内存;并发执行大部分工作(包括标记、重定位、引用处理等)
  • 优点:几乎不暂停应用线程;高可伸缩性
  • 适用场景:实时系统、大规模缓存服务、金融高频交易系统
  • 启用参数:-XX:+UseZGCJDK 11+

Shenandoah GC

  • 特点:由 Red Hat 主导开发;类似 ZGC,强调低延迟;支持并发压缩,减少 STW 时间
  • 优点:适合需要快速响应且堆较大的应用
  • 适用场景:Web 服务、云原生服务、大数据分析
  • 启用参数:-XX:+UseShenandoahGCJDK 12+

对比表格总结

回收器新生代/老年代是否并发延迟吞吐是否压缩适用场景
Serial新生代一般小内存,客户端程序
Serial Old老年代一般小内存,CMS备用
Parallel Scavenge新生代一般吞吐优先
Parallel Old老年代一般吞吐优先
CMS老年代一般响应优先
G1整体较低较高通用,大堆
ZGC整体很低超低延迟,大堆
Shenandoah整体很低超低延迟,大堆

如何选择合适的垃圾回收器?

应用类型推荐GC
吞吐优先(如离线计算、批处理)Parallel Scavenge + Parallel Old
响应优先(如 Web 服务、APIG1 GC / CMSJDK 8
超低延迟(<10msZGC / Shenandoah
小内存、嵌入式系统Serial GC
微服务、容器化部署G1 GC / ZGC
  1. G1 收集器的工作原理?

G1Garbage-First)垃圾收集器 是 Java 中一种面向服务端应用的垃圾回收器,适用于大堆内存(几 GB到几十GB),目标是 在高吞吐的同时实现可预测的低延迟。它打破了传统分代模型(新生代 + 老年代),采用了一种 分区式(Region-based) 的内存管理方式。

核心设计思想

堆内存划分:Region 分区

  • G1 将整个 Java 堆划分为多个大小相等的独立区域(Region),默认大小为 1MB ~ 32MB(由堆大小决定)
  • 每个 Region 可以是:Eden区、Survivor区、老年代(Old Region)、大对象区(Humongous Region

注意:这种设计使得 G1 不再严格区分新生代和老年代,而是根据需要动态分配 Region 类型

并行与并发结合

  • G1 是 多线程并行 + 并发收集器:
    • 并行(Parallel):多个 GC 线程同时工作,提高效率
    • 并发(Concurrent):部分阶段可以与用户线程一起执行,降低 STWStop-The-World)时间

可预测的停顿时间模型

  • 用户可通过 -XX:MaxGCPauseMillis=N 设置期望的最大 GC 停顿时间(如 200ms)
  • G1 会根据历史数据选择最优的 Region 回收集合(Collection Set, CSet),优先回收垃圾最多的 Region

主要工作流程

初始标记

  • 作用:标记从根节点(GC Roots)直接可达的对象
  • 特点:需要 Stop the World;时间非常短(通常<1ms

并发标记

  • 作用:从根节点开始遍历所有存活对象,进行可达性分析
  • 特点:与用户线程并发运行;标记过程中可能有对象被修改(使用 SATB 算法记录变化)

最终标记

  • 作用:处理并发标记期间因程序运行而变动的对象引用
  • 特点:需要 Stop the World;使用 SATBSnapshot-At-The-Begin)记录的变更进行修正

筛选回收:

  • 作用:
    • 对各个 region 的回收价值和成本排序(Garbage First
    • 选择一组 region 组成 Collection SetCSet
    • 将存活对象复制到新的空 region
    • 清除旧 region 中的所有对象
  • 特点:可能伴随着 Stop the World;实现了增量回收(Incremental Collection

G1 的关键机制

RSetRemembered Set

  • 每个 Region 都有一个 RSet,用于记录 外部指向该 Region 内对象的引用。
  • 在并发标记或回收时,避免扫描整个堆来查找跨 Region 引用。
  • 提升 GC 性能,但占用额外内存。

SATBSnapshot-At-The-Beginning

  • 在并发标记开始前对堆做一个快照(Snapshot
  • 所有在并发标记期间被修改的对象都会被记录下来
  • 最终标记阶段会对这些变化进行补偿处理,确保正确性

Humongous Region(巨型对象区)

  • 用于存放大于等于 Region 一半大小的对象
  • 占用连续的多个 Region
  • 回收代价较高,建议尽量避免创建过大的对象

G1 的优势总结

特性描述
低延迟 + 高吞吐兼顾可预测停顿时间,适合对响应敏感的应用
分区式管理更灵活地管理内存,支持大堆
并行与并发结合多线程 + 并发标记,提升性能
压缩整理内存减少内存碎片,避免 Full GC
自适应回收策略根据垃圾多少动态选择回收区域
  1. 如何判断一个对象是否可以被回收?

Java 中,判断一个对象是否可以被回收,主要依赖于 垃圾回收器(GC) 对对象的“可达性分析”(Reachability Analysis)。JVM 通过一系列称为 GC Roots 的根对象出发,沿着引用链向下查找,如果某个对象无法通过任何路径从 GC Roots 到达,则该对象被认为是不可达的,即可被回收的对象

常见的 GC Roots 类型

类型实例
虚拟机栈中的局部变量表中的引用对象方法中定义的 Object o = new Object()
静态属性引用的对象public static String cache;
常量引用对象(final staticpublic static final String VERSION = "1.0";
本地方法(Native)引用的对象JNI 引用的 ClassLoader, FileDescriptor
活跃线程(Thread)对象本身正在运行的线程对象
  1. CMSG1 的区别?
特性CMSG1
主要目标适用年代低延迟(Low Latency),适合响应敏感型应用可预测的低延迟 + 高吞吐量,兼顾响应与吞吐
适用年代老年代回收器整体管理堆(新生代 + 老年代)
堆结构分代模型(新生代 + 老年代)分区模型(Region-based),不再严格分代
GC 算法标记-清除(Mark-Sweep标记-整理 + 复制算法
内存碎片问题存在内存碎片无内存碎片(回收时整理)
并发阶段支持并发标记,减少 STW 时间并行 + 并发执行
吞吐量相对较低较高
适用堆大小几百 MB 到 几 GB从几 MB 到几十 GB,甚至 TB
是否推荐使用(JDK9+不再默认启用,逐步淘汰JDK9 默认 GC,持续优化中
Full GC 触发机制并发失败或晋升失败会导致 Full GC回收效率高,Full GC 次数少

OOM 排查与调优

  1. 常见的 OOM 类型有哪些?如何定位?
OOM类型描述常见原因
Java heap space堆内存溢出对象创建过多且未释放,内存泄漏或堆设置过小
GC overhead limit exceededGC 时间占比过高系统花大量时间进行垃圾回收,但回收效果差
PermGen spaceJDK 7 及以前)永久代溢出加载类过多(如动态代理、反射、JSP 编译等)
MetaspaceJDK 8+元空间溢出类元数据加载过多,未正确卸载类加载器
unable to create new native thread无法创建新线程线程数超过系统限制,或线程栈过大
Direct buffer memory直接内存溢出使用 NIOByteBuffer.allocateDirect() 分配过多直接内存
OutOfMemoryError: Requested array size exceeds VM limit请求的数组大小超过虚拟机限制创建超大数组(如 new int[Integer.MAX_VALUE])
Map failed内存映射失败使用 mmap() 或内存映射文件时内存不足

如何定位和分析?

方法一:查看异常堆栈信息

当发生 OOM 时,JVM 会打印异常堆栈信息,包含类型和大致位置:

java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3332)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
    ...

注意:注意堆栈中的关键类和方法名,有助于判断是哪部分代码导致的问题。

方法二:生成并分析 Heap Dump 文件

  • 启动参数添加自动生成 Heap Dump
    • -XX:+HeapDumpOnOutOfMemoryError
    • -XX:HeapDumpPath=/path/to/dump.hprof
  • 使用工具分析 Heap Dump
    • MATEclipse Memory Analyzer):
      • 查看最大对象占用
      • 找出 GC Roots 路径
      • 分析内存泄漏路径
    • VisualVM / JProfiler / YourKit
      • 图形化展示内存快照
      • 支持对比多个 dump 文件
    • Arthas(阿里开源)

方法三:使用 JVM 工具实时监控

  • jstat 查看 GC 情况:
    • jstat -gc <pid> 1000 10,观察 EU, OUEdenOld 区使用率),以及 YGC, FGC 频率。
  • jmap 查看堆内对象分布:
    • jmap -histo <pid> | head -n 20,显示前 20 个占用内存最多的类。
  • jconsole / VisualVM 实时查看堆内存、线程、类加载情况
  1. 如何生成并分析 Head Dump?

生成 Head Dump 的方式

  • JVM 启动参数自动生成
    • jmap -dump:format=b,file=<filename>.hprof <pid>
  • 使用命令手动触发(运行中程序)
    • jcmd <pid> GC.heap_dump /path/to/heapdump.hprof
  • 通过 Arthas 实时导出
# 连接目标进程
arthas-boot <pid>

# 导出 heap dump 文件
heapdump /data/dumps/heapdump.hprof

如何分析 Head Dump

MATEclipse Memory Analyzer

  • 打开 MAT,点击 "Open a Histogram" 或 "Open a Dominator Tree"
  • 查看占用内存最多的类
  • 右键某个类 → "Merge Shortest Path to GC Roots" → 分析引用链

VisualVMJDK

  • 在终端输入 jvisualvm 启动
  • 加载 Heap Dump 文件
  • 查看 SummaryClassesInstances 等标签页
  • 使用 "GC Roots" 查看对象引用路径

JProfiler

YourKit

分析常用技巧

技巧描述
Histogram 视图查看每个类的实例数量和占用内存大小
Dominator Tree 视图查看哪些对象占用了最多内存,并能追踪其引用来源
Leak Suspects 报告MAT 自动生成可疑内存泄漏报告
Path to GC Roots查看某个对象为什么没有被回收
Compare with Another Dump对比两个 dump 文件,查看对象增长趋势

常见内存泄漏场景分析

场景表现解决方法
集合类未释放HashMapArrayList 实例数异常增长检查是否作为缓存、监听器等长期持有
ThreadLocal 泄漏ThreadLocalMap 占用大量内存使用完后调用 remove()
监听器未注销如事件监听器、观察者模式对象不释放检查注册逻辑,确保注销机制
ClassLoader 泄漏ClassLoader 实例持续增加检查动态加载类是否卸载
缓存未清理缓存对象持续增长使用弱引用(WeakHashMap)、设置过期策略
  1. JVM 常用调优参数有哪些?

堆内存相关参数

参数说明实例
-Xms初始堆大小(默认物理内存的1/64-Xms512m
-Xmx最大堆大小(默认物理内存的1/4-Xmx2g
-XX:NewSize新生代初始大小-XX:NewSize=256m
-XX:MaxNewSize新生代最大大小-XX:MaxNewSize=1g
-XX:NewRatio老年代与新生代比例(默认 2-XX:NewRatio=3(老年代:新生代 = 3:1
-XX:SurvivorRatioEden 区与 Survivor 区比例(默认 8)-XX:SurvivorRatio=4Eden:Survivor = 4:1)

推荐:设置 Xms == Xmx,避免堆动态伸缩带来的性能波动。

GC G1 相关参数

参数说明示例
-XX:+UseSerialGC使用 Serial GC(单线程,适合小内存)-
-XX:+UseParallelGC使用 Parallel Scavenge(多线程,吞吐优先)
-XX:+UseConcMarkSweepGC使用 CMS(低延迟,已废弃)
-XX:+UseG1GC使用 G1 GC(推荐,默认 JDK9+
-XX:MaxGCPauseMillis设置目标最大停顿时间(毫秒)-XX:MaxGCPauseMillis=20
-XX:G1HeapRegionSize设置 G1 Region 大小(1\~32MB-XX:G1HeapRegionSize=4M
-XX:ParallelGCThreads并行 GC 线程数-XX:ParallelGCThreads=8
-XX:ConcGCThreads并发 GC 线程数-XX:ConcGCThreads=4

推荐:使用 G1 GC,兼顾低延迟和高吞吐。

元空间相关参数

参数说明示例
-XX:MetaspaceSize初始元空间大小-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize元空间最大值(不设上限可能 OOM)-XX:MaxMetaspaceSize=512m
-XX:+UseCompressedClassPointers是否压缩类指针(节省内存)默认开启
-XX:CompressedClassSpaceSize压缩类指针空间大小-XX:CompressedClassSpaceSize=256m

推荐:生产环境建议设置 MaxMetaspaceSize,防止元空间无限增长。

栈内存相关参数

参数说明示例
-Xss每个线程的栈大小(影响最大线程数)-Xss512k
-XX:ThreadStackSize同上(部分 JVM 可能使用该参数)-XX:ThreadStackSize=512(单位 KB)

注意:栈大小不宜过大,否则可能导致 unable to create new native thread

JVM 工具使用

  1. 你常用的 JVM 监控和诊断工具有哪些?
  • jstat:实时查看 GC 状态、堆内存使用情况
  • jmap:生成 Heap Dump、查看对象内存分布
  • jstack:查看线程堆栈信息,排查死锁、阻塞等问题
  • jcm:执行多种 JVM 操作(如 GCHeap Dump
  • VisualVM:图形化监控 JVM 内存、线程、GC,支持插件扩展
  • MAT:分析 Heap Dump 文件,定位内存泄漏
  • Arthas:线上 JVM 实时诊断,支持类加载、方法追踪、线程分析等
  • Prometheus + Grafana + JVM Exporter:生产环境可视化监控 JVM 指标(GC、内存、线程等)
  1. 如何使用 jstatjmapjstack?
  • jstat(查看 JVM 内存和 GC 状态)
# 查看 GC 情况,每 1 秒刷新一次
jstat -gc <pid> 1000

关键指标:
S0U/S1U:Survivor 使用
EU/OU:Eden 和 Old 区使用
YGC/FGC:Young 和 Full GC 次数与耗时
  • jmap(查看堆内存和生成 dump
# 查看堆内存概览
jmap -heap <pid>

# 查看对象数量分布(前 20)
jmap -histo <pid> | head -n 20

# 生成 Heap Dump 文件
jmap -dump:format=b,file=dump.hprof <pid>
  • jstack(查看线程堆栈)
# 导出所有线程堆栈
jstack <pid> > thread_dump.log

# 查看是否有死锁信息
jstack <pid> | grep -A 20 "deadlock"

3. Arthas 能做什么?举几个常用命令?

Arthas 是阿里巴巴开源的 Java 诊断工具,被誉为“Java 程序员的瑞士军刀”,能够在不修改代码、不停机的情况下,实时诊断和分析运行中的 Java 程序

  • 实时查看 JVM 状态:内存、线程、GC、系统属性等
  • 方法执行监控:查看方法调用次数、耗时、入参、返回值
  • 类加载信息:查看已加载类、反编译类文件
  • 性能分析:方法耗时统计(trace/profile
  • 动态修改日志级别:无需重启即可调整 log4j/logback 的日志级别
  • 排查线上问题:死锁、CPU 飙高、内存泄漏、方法异常等
命令用途
dashboard实时查看系统、JVM、线程、内存概览
thread查看所有线程状态,查找阻塞/死锁线程 thread -n 3 查看 CPU 使用最高的 3 个线程
jvm查看 JVM 参数、内存模型、类加载器等信息
sc / sm查找已加载类(sc * Controller)、方法(sm com.example.UserController.*
jad反编译类(如:jad com.example.UserService
watch监控方法参数、返回值、异常(如:watch com.example.UserService getUser "{params, returnObj}")
trace方法内部调用链路和耗时分析(如:trace com.example.OrderService createOrder
heapdump导出堆快照用于内存分析(类似 jmap
logger查看和修改日志级别(如:logger -n ROOT -l DEBUG
profilerCPU/内存采样,生成火焰图分析热点方法

字节码与执行

  1. 说说 Class 文件的结构?

一个 Java Class 文件是编译后的二进制文件,结构清晰,主要包括以下部分(按顺序)

结构项说明
魔数(Magic固定值 0xCAFEBABE,标识这是一个 Java Class 文件
版本号(Version包括次版本号和主版本号,如 52.0 表示 JDK 8
常量池(Constant Pool存放字面量(如字符串、常量)和符号引用(如类名、方法名)
访问标志(Access Flags标识类的访问权限,如 publicabstractfinal
类索引、父类索引、接口索引指向常量池,描述当前类、父类、实现的接口
字段表集合(Fields描述类中的变量(包括静态变量、实例变量)
方法表集合(Methods描述类中的方法(包括构造方法、静态方法、实例方法)
属性表(AttributesCode 属性:存放方法的字节码指令

javap -v *.class可查看 Class 文件的反编译结构。

  1. javap 工具的作用是什么?

javapJDK 自带的一个 Java 反汇编工具,主要用于查看 .class 文件的 字节码结构和类信息。它可以帮助你理解程序底层实现、调试优化问题、分析第三方库等。

  • 🛠️ 调试代码行为:查看是否真的执行了某段逻辑(如内联优化)
  • 📈 性能优化:查看方法调用方式(虚方法 vs 静态绑定)
  • 🔐 安全审计:检查敏感操作是否被正确编译
  • 🧪 学习 JVM 字节码:理解 Java 编译器如何将 Java 代码翻译为 JVM 指令
  1. 什么是 JIT 编译?它对性能有什么影响?

JITJust-In-Time)编译 是 JVM 在程序运行时将字节码动态编译为本地机器码的技术,以提升执行效率。

  • Java 程序最初以字节码运行在解释器上;
  • JVM 会监控方法的执行频率,热点代码(HotSpot) 会被 JIT 编译为高效的机器码;
  • 编译后的代码缓存在 CodeCache 中,后续调用直接执行机器码。

对性能的影响

  • ⬆️ 提升执行速度:本地机器码比解释执行快很多,尤其对频繁调用的方法
  • 🚀 启动慢、运行快:初期解释执行较慢,热点方法被 JIT 编译后性能大幅提升
  • 🔁 延迟优化:可能延迟编译,优先执行解释模式,适合长期运行的服务
  • 🧠 占用额外内存:编译后的机器码存放在 CodeCache,占用本地内存

推荐记忆推荐

画图法:

  • 绘制 JVM 内存结构图、类加载流程图、GC 流程图。

微信图片_20250512213210_3.png

微信图片_20250513203557_4.png

口诀法:

  • JVM 五区域:堆栈方法区,程序计数器,本地方法栈”
  • “类加载五步走:加验准解初”

对比记忆:

  • 对比 SerialParallelCMSG1 四种垃圾收集器的特点
  • 对比 PermGenMetaspace

实战演练:

  • 模拟 OOM 场景并分析日志
  • 使用 Arthas 实时查看线程、内存、GC 状态