1. class 文件的结构?

class 文件是一组以 8 字节为单位的二进制流,无分隔符。主要结构:

  1. 魔数(Magic Number):前 4 字节 CAFEBABE,识别文件类型
  2. 版本号:minor_version + major_version(如 52 = Java 8,61 = Java 17)
  3. 常量池(Constant Pool):最大的部分,存所有字面量和符号引用(类名、方法名、字段名等)
  4. 访问标志:public/final/abstract/interface 等修饰符
  5. 类索引、父类索引、接口索引:指向常量池中的类描述
  6. 字段表、方法表:各成员的描述,方法表里就是字节码指令
  7. 属性表:附加信息(Code 属性存方法体、Exceptions、LineNumberTable 等)
用 javap 查看字节码
# javap 是 JDK 自带的反汇编工具(输出字节码指令,不是 Java 源码)
javap -v Hello.class          # 详细:常量池、方法字节码、行号表
javap -c Hello.class          # 只反汇编字节码指令
javap -p Hello.class          # 包含 private 成员

// 源代码
public static int add(int a, int b) {
    return a + b;
}

// 字节码(javap -c 输出)
0: iload_0        // 把局部变量表第 0 个 int 压栈(a)
1: iload_1        // 压入 b
2: iadd           // 弹出两数相加,结果压栈
3: ireturn        // 返回栈顶 int

🎯 面试要点

  • 符号引用 → 直接引用 的解析就发生在类加载"解析"阶段
  • class 版本号不兼容报 UnsupportedClassVersionError(编译版本高于运行版本)
  • 字节码是 JVM 的指令集(类似汇编),JIT 会把热点字节码编译成机器码

2. 常用 JVM 诊断工具怎么用?

JDK 自带工具全家桶
jps -l                            # 列出 Java 进程(拿 pid)
jps -v                            # 附带 JVM 参数

jstat -gc <pid> 1000               # 每秒输出 GC 统计(各区容量、GC 次数、耗时)
jstat -gcutil <pid> 1000          # 输出各区使用率百分比,看瓶颈更快

jmap -heap <pid>                  # 堆配置与当前使用概况
jmap -histo <pid>                 # 堆内对象直方图(找大对象/泄漏源头)
jmap -dump:format=b,file=x.hprof <pid>   # 堆转储,配合 MAT 分析

jstack <pid>                       # 线程快照(找死锁、卡住的线程、CPU 高的线程)
jstack -l <pid>                    # 附带锁信息(找到 BLOCKED 的锁持有链)

jcmd <pid> GC.heap_info          # 新一代全能工具,JDK8+ 推荐
jinfo <pid>                        # 查看 JVM 参数

Arthas(阿里开源,生产排查神器):在线诊断不用重启——dashboard(实时面板)、thread -n 3(最忙的线程)、trace(方法耗时链路)、watch(观测方法入参返回值)、jad(反编译线上代码)、sc/sm(查类/方法)。

🎯 面试要点

  • jstat -gcutil 三个关键指标:E(Eden)、O(Old)、FGC(Full GC 次数)
  • jmap dump 大堆会卡住应用(STW 级别),生产慎用;优先用 jcmd GC.heap_dump 或 Arthas heapdump
  • jstack 线程状态关键字:RUNNABLE/BLOCKED/WAITING/TIMED_WAITING + "waiting for monitor"(等锁)

3. 线上 CPU 飙高 100%,怎么排查?(高频实战题)

  1. top 找到 CPU 最高的进程 PID
  2. top -Hp <pid> 找到进程内 CPU 最高的线程号 TID
  3. printf "%x\n" <TID> 把十进制线程号转十六进制 nid
  4. jstack <pid> | grep -A 20 "nid=0x..." 定位到具体线程和代码行
  5. 看到代码后分析:死循环?正则回溯?GC 线程异常?锁竞争自旋?
完整命令链(Linux)
top                                   # ① 找高 CPU 进程
top -Hp 12345                         # ② 进程内找高 CPU 线程 TID(如 15250)
printf "%x\n" 15250                   # ③ 得到 3b92
jstack 12345 | grep -A 30 "0x3b92"    # ④ 线程栈,定位代码

# 更省事:Arthas
./as.sh <pid>
dashboard                             # 直接看各线程 CPU,不必转进制
thread -n 3                           # 最忙的 3 个线程及栈

🎯 面试要点

  • 全程只读排查,无侵入,生产可用
  • 死循环常见原因:while(true) 无退出条件、HashMap 并发 put(JDK7 死链)、正则灾难性回溯
  • CPU 高不一定是业务线程:GC 线程(-XX:+UseG1GC 的 G1 Concurrent Marking)也会占 CPU

4. 内存泄漏如何排查?

先判断是泄漏还是容量不足:

  1. jstat -gcutil <pid> 观察 O 区(老年代)趋势
  2. jmap -histo:live <pid> 多次采样对比:某类对象数量只增不减 → 泄漏源头
  3. jmap -dump:format=b,file=heap.hprof <pid> 导出堆
  4. MAT / jvisualvm 分析:Dominator Tree(支配树)找大对象;Leak Suspects 直接给嫌疑报告;GC Roots 路径看谁还引用着它

常见泄漏场景:静态集合只增不减(缓存忘清)、ThreadLocal 用完不 remove(线程池线程长活)、监听器/回调未注销、连接/IO 流未关闭(间接泄漏)、类加载器泄漏(热部署)

🎯 面试要点

  • MAT 三大武器:Leak Suspects / Dominator Tree / Path to GC Roots(排除软弱虚引用)
  • 生产 dump 前评估:大堆 dump 可能 OOM 加剧,优先 Arthas heapdump 或夜间操作
  • 排查泄漏的代码侧技巧:Java Flight Recorder(JFR)持续采样,不用停机

5. 线程死锁如何发现和定位?

死锁四条件:互斥、持有并等待、不可剥夺、循环等待。定位方法:

  1. jstack <pid> 输出末尾会有 Found one Java-level deadlock 的完整报告,直接告诉你两个线程互相等待的锁
  2. 或者观察:多个线程都处于 BLOCKED 且都在 "waiting for monitor lock"
  3. Arthas:thread -b 直接找阻塞其他线程的线程(blocking thread)
死锁代码示例
Object lockA = new Object();
Object lockB = new Object();

// 线程 1:先 A 后 B;线程 2:先 B 后 A → 循环等待,死锁
new Thread(() -> { synchronized (lockA) {
    synchronized (lockB) { ... } } }).start();
new Thread(() -> { synchronized (lockB) {
    synchronized (lockA) { ... } } }).start();

🎯 面试要点

  • 预防:按固定顺序加锁、锁超时(tryLock)、尽量缩小锁粒度
  • jstack 的 deadlock 报告是检测死锁最快的官方手段

🎤 常见面试追问

  1. CPU 飙高 100% 的排查完整命令链?——top 找进程 → top -Hp 找线程 → printf "%x" 转十六进制 → jstack | grep "0xnid" 定位代码。Arthas 的 dashboard/thread -n 3 更快。
  2. 怎么判断是内存泄漏还是堆不够?——Full GC 后老年代占用不降且持续涨 → 泄漏;能降但频率高 → 堆太小或对象过大。jstat -gcutil 看趋势。
  3. jmap dump 会不会影响线上?——会!大堆 dump 会触发 STW 级别的停顿(GC roots 扫描),生产慎用;优先 Arthas heapdump 或夜间操作。
  4. 线程卡死(无响应)怎么查?——jstack 看线程状态:BLOCKED=等锁(找锁持有者)、WAITING=等唤醒(wait 没 notify)、RUNNABLE 但不动=死循环/外部调用没超时。
  5. class 文件版本不兼容报什么错?——UnsupportedClassVersionError:编译版本(如 61=JDK17)高于运行版本,用 javap -v 或 java -version 对版本。

📖 名词解释(本页术语)

术语 大白话解释
字节码Java 编译产物的"中间指令"(.class 文件内容),JVM 执行的就是它。javap -c 可以反汇编查看。
魔数(CAFEBABE)class 文件开头的固定 4 字节,用于识别"这是 class 文件"(像文件的身份证号)。
jps / jstat / jmap / jstackJDK 诊断工具:jps 找进程;jstat 看 GC/内存统计;jmap 看堆和 dump;jstack 看线程栈(死锁/卡死)。
Arthas阿里开源的在线诊断工具:不用重启就能看线程(thread)、方法耗时(trace)、反编译(jad)、改日志级别等。
堆转储(Heap Dump)把堆内存快照导出成文件(.hprof),用 MAT/jvisualvm 分析谁占内存、谁引用着它。
死锁两个线程互相持有对方需要的锁,互不相让。jstack 末尾会直接打印 deadlock 报告。
MAT(Memory Analyzer)Eclipse 的内存分析工具:Leak Suspects 自动找泄漏嫌疑,Dominator Tree 看大对象。
nid(线程 ID)JVM 线程在 OS 层的 ID(十六进制)。jstack 输出里 "nid=0x..." 对应 top -H 看到的线程号转十六进制。
⚠️ 本页面由 AI 生成,内容仅供参考,请以官方文档和实际源码为准。