JVM异常识别¶
一、内存相关异常(最常见,占 60%+)¶
这些异常直接来自JVM内存区域,日志里通常带“内存”字样 。
1. OutOfMemoryError: Java heap space
识别标志:堆内存(Heap)满了,无法分配新对象 。
常见原因:
- 对象创建过多(如无限循环 new)。
- 内存泄漏(缓存没清理、Session 没释放)。
- 堆设置太小(-Xmx 不够)。
排查步骤:
- jstat -gc
5s 看 GC 情况(Full GC 频繁)。 - jmap -dump:format=b,file=heap.hprof
导出堆 dump。 - 用 MAT/Eclipse Memory Analyzer 分析大对象、泄漏。
2. OutOfMemoryError: GC overhead limit exceeded
识别标志:GC 占用时间超过 98%,回收不到 2% 内存 。
原因:内存基本满了,GC 一直在“白忙活”。
解决:增大堆或查内存泄漏。
3. OutOfMemoryError: Metaspace
识别标志:方法区(元空间)满了,通常是动态加载类太多 。
原因:Spring Boot 热部署工具、大量代理类。
解决:-XX:MaxMetaspaceSize=512m
- StackOverflowError
识别标志:栈溢出 。
原因:递归太深、栈帧太大(局部变量多)。
排查:jstack
代码示例:
public class StackOverflowDemo {
public static void infiniteRecursion() {
infiniteRecursion(); // 无限递归
}
public static void main(String[] args) {
infiniteRecursion();
}
}
解决:增大栈 -Xss2m,但治标不治本。
二、GC 相关异常(日志里有 GC 关键字)¶
- 频繁 Minor GC(新生代 GC)
识别标志:日志里 ParNew、G1 Young 非常频繁。
原因:对象创建速度太快,新生代满了。
解决:增大 Eden 区 -XX:NewRatio=2(老年代:新生代=2:1)。
- 频繁 Full GC(老年代 + 全堆 GC)
识别标志:日志里 Full GC、Old Gen 频繁,STW 时间长(Stop The World)。
原因:
- 大对象直接进老年代。
- Survivor 区不够用,对象提前晋升。
- 内存泄漏。
- 排查:jstat -gc
看 FGC 次数、OGC(老年代使用量)。
- 排查:jstat -gc
日志示例(用 -XX:+PrintGCDetails 开启):
[GC (Allocation Failure) ... Eden 空间满了]
[Full GC (Ergonomics) ... 老年代满了,STW 1.2s]
三、线程与锁相关异常¶
- java.lang.OutOfMemoryError: unable to create native thread
识别标志:无法创建新线程。
原因:
- 线程数超过系统限制(Linux ulimit -u)。
- 每个线程栈太大(默认 1MB)。
-
解决:减少线程数,用线程池;-Xss512k 减小栈。
-
死锁(Deadlock)——不是异常,是线程状态
识别标志:
- jstack
输出里有 java.lang.Thread.State: BLOCKED (on object monitor)。 - 多个线程互相持有对方需要的锁。
排查(金标准):
- 通过jps找到当前机器上运行的Java进程及其pid
- jstack
> thread.txt。 - 搜索 deadlock,自动分析。
代码示例(经典死锁):
public class DeadlockDemo {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock1) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Thread 1 done");
}
}
}).start();
new Thread(() -> {
synchronized (lock2) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) {
System.out.println("Thread 2 done");
}
}
}).start();
}
}
jps常用参数¶
常见几个你用得上的参数:
+ jps:只显示 pid 和简短的类名。
+ jps -l:显示主类的完整类名或 JAR 路径,更容易区分进程。
+ jps -v:显示启动该 Java 进程时使用的 JVM 参数(如 -Xmx 等)。
+ jps -m:显示 main 方法的参数信息。
四、类加载与启动异常¶
- ClassNotFoundException / NoClassDefFoundError
识别标志:类找不到。
原因:
- ClassNotFoundException:Class.forName() 动态加载失败。
-
NoClassDefFoundError:运行时类路径有问题(编译时有,运行时没了)。 排查:查 classpath、Maven 依赖。
-
LinkageError / NoSuchMethodError
识别标志:类冲突,通常 jar 包版本不一致。
原因:多个 jar 有同名类但方法签名不同。
五、生产环境监控命令速查表¶
| 问题类型 | 命令 | 作用 |
|---|---|---|
| GC 监控 | jstat -gc |
实时看 GC 频率、堆使用量 |
| 堆 Dump | jmap -dump:format=b,file=heap.hprof |
导出堆快照分析内存泄漏 |
| 线程 Dump | jstack |
查死锁、线程状态 |
| CPU 热点 | top -Hp |
看哪个线程 CPU 高,再 jstack |
| 进程列表 | jps -l | 列出所有 Java 进程 |
高级工具:
- VisualVM / JConsole(本地开发)。
- Arthas(线上无侵入诊断)。
实战建议¶
1. 先开 GC 日志(项目启动参数):
-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
这样异常发生时就有“前因后果”。
2. 异常优先级排查流程:
OOM? → jmap 堆dump → MAT 分析大对象
SOF? → jstack 看栈帧
CPU 高? → top → jstack 热点线程
GC 慢? → jstat GC 统计
3. 预防胜于治疗:
- 生产环境默认加:-Xmx4g -Xms4g -XX:MaxMetaspaceSize=256m -XX:+HeapDumpOnOutOfMemoryError。
- 用线程池代替 new Thread()。
- 缓存要设过期时间。
常用命令使用技巧¶
jstack 命令¶
# 基础用法(推荐加 -l)
jstack -l <PID> > thread.dump
# 强制生成(进程卡死时用)
jstack -F <PID>
# 混合模式(Java + Native 栈)
jstack -m <PID>
- -l:额外显示锁信息(生产必加)。
- 输出重定向到文件,便于分析。
jstack 输出解读(关键结构)¶
jstack 输出分成三部分 :
- Header:JVM 版本、时间戳。
- 每个线程的栈信息:最重要!
- Deadlock 检测:如果有死锁,会自动标出。
线程状态(java.lang.Thread.State)
| 状态 | 含义 | 常见原因 |
|---|---|---|
| RUNNABLE | 可运行/执行中 | 正常、CPU 高可能在这里 |
| WAITING | 等待(无限期) | Object.wait()、Thread.join()、LockSupport.park() |
| TIMED_WAITING | 定时等待 | Thread.sleep()、Lock.lockInterruptibly() |
| BLOCKED | 阻塞等待锁 | synchronized 等锁,死锁在这里! |
| NEW | 新建,未启动 | |
| TERMINATED | 已终止 |
Locked ownable synchronizers:当前线程持有的锁。