跳转至

JVM异常识别

一、内存相关异常(最常见,占 60%+)

这些异常直接来自JVM内存区域,日志里通常带“内存”字样 。

1. OutOfMemoryError: Java heap space

识别标志:堆内存(Heap)满了,无法分配新对象 。

常见原因:

  • 对象创建过多(如无限循环 new)。
  • 内存泄漏(缓存没清理、Session 没释放)。
  • 堆设置太小(-Xmx 不够)。

排查步骤:

  1. jstat -gc 5s 看 GC 情况(Full GC 频繁)。
  2. jmap -dump:format=b,file=heap.hprof 导出堆 dump。
  3. 用 MAT/Eclipse Memory Analyzer 分析大对象、泄漏。

2. OutOfMemoryError: GC overhead limit exceeded

识别标志:GC 占用时间超过 98%,回收不到 2% 内存 。

原因:内存基本满了,GC 一直在“白忙活”。

解决:增大堆或查内存泄漏。

3. OutOfMemoryError: Metaspace

识别标志:方法区(元空间)满了,通常是动态加载类太多 。

原因:Spring Boot 热部署工具、大量代理类。

解决:-XX:MaxMetaspaceSize=512m

  1. StackOverflowError

识别标志:栈溢出 。

原因:递归太深、栈帧太大(局部变量多)。

排查:jstack 看线程栈,看是不是死递归。

代码示例:

public class StackOverflowDemo {
    public static void infiniteRecursion() {
        infiniteRecursion(); // 无限递归
    }
    public static void main(String[] args) {
        infiniteRecursion();
    }
}

解决:增大栈 -Xss2m,但治标不治本。

二、GC 相关异常(日志里有 GC 关键字)

  1. 频繁 Minor GC(新生代 GC)

识别标志:日志里 ParNew、G1 Young 非常频繁。

原因:对象创建速度太快,新生代满了。

解决:增大 Eden 区 -XX:NewRatio=2(老年代:新生代=2:1)。

  1. 频繁 Full GC(老年代 + 全堆 GC)

识别标志:日志里 Full GC、Old Gen 频繁,STW 时间长(Stop The World)。

原因:

  • 大对象直接进老年代。
  • Survivor 区不够用,对象提前晋升。
  • 内存泄漏。
    • 排查:jstat -gc 看 FGC 次数、OGC(老年代使用量)。

日志示例(用 -XX:+PrintGCDetails 开启):

[GC (Allocation Failure) ...  Eden 空间满了]
[Full GC (Ergonomics) ... 老年代满了,STW 1.2s]

三、线程与锁相关异常

  1. 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 方法的参数信息。

四、类加载与启动异常

  1. ClassNotFoundException / NoClassDefFoundError

识别标志:类找不到。

原因:

  • ClassNotFoundException:Class.forName() 动态加载失败。
  • NoClassDefFoundError:运行时类路径有问题(编译时有,运行时没了)。 排查:查 classpath、Maven 依赖。

  • LinkageError / NoSuchMethodError

识别标志:类冲突,通常 jar 包版本不一致。

原因:多个 jar 有同名类但方法签名不同。

五、生产环境监控命令速查表

问题类型 命令 作用
GC 监控 jstat -gc 1s 100 实时看 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 输出分成三部分 :

  1. Header:JVM 版本、时间戳。
  2. 每个线程的栈信息:最重要!
  3. 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:当前线程持有的锁。