6. 后端开发常见 JVM 问题
本文我们将探讨:后端开发常见 JVM 问题与排查工具。
作为后端开发人员,我们在生产环境中总会遇到程序崩溃、卡顿或者服务器 CPU 飙高的情况。在本文中,我们将分析最常见的两个内存溢出错误,以及如何使用 JDK 自带的四大常用排查命令来进行问题定位。
1. 内存溢出(OOM - Out Of Memory)
当 JVM 需要给数据分配内存,却发现整个内存区域已经全被占满,且垃圾回收器(GC)拼尽全力也清理不出任何空间时,JVM 就会抛出 java.lang.OutOfMemoryError 错误,程序随后就会崩溃挂掉。
生产环境中最常遇到的有两种 OOM:
① 堆内存溢出(java.lang.沉没/OutOfMemoryError: Java heap space)
- 为什么发生:我们在第三部分讲过,堆是存放对象实体的地方。如果你不断创建对象,且这些对象由于被某些长寿对象(如静态集合、全局 Service)引用而无法被 GC 回收(即发生了内存泄漏),堆内存迟早会被耗尽。
- 后端常见场景:
- 一次性查询了太多数据:比如写了一条没有加
LIMIT分页的 SQL,直接把数据库里几百万条用户数据一次性查出来放到了一个List里面,堆内存瞬间被撑爆。 - 死循环创建对象:代码里写了死循环,不停地
new对象并往一个全局Map里塞。
- 一次性查询了太多数据:比如写了一条没有加
② 元空间溢出(java.lang.OutOfMemoryError: Metaspace)
- 为什么发生:元空间存放的是类的信息(设计图纸)。如果程序在运行期间,不断加载了成千上万个新的类,元空间就会爆掉。
- 后端常见场景:
- 很多现代框架(如 Spring 的某些动态代理机制)会在运行期间动态生成类。如果框架使用不当,或者引入了某些会无限生成动态类的第三方库,就会导致元空间溢出。
2. JDK 自带的四大核心排查命令
当线上服务器的 Java 程序突然变慢,或者直接崩掉时,我们不能盲目猜原因。JDK 在安装时就已经自带了一套命令行小工具,可以帮我们直接看穿 JVM 内部的现状。
假设我们已经通过 Linux 的 top 命令找到了那个出问题的 Java 程序的进程 ID(假设是 12345)。接下来就可以派出这四大金刚:
① jps(Java Virtual Machine Process Status Tool)
- 作用:查看当前计算机上运行了哪些 Java 进程。
- 怎么用:直接输入
jps -l。它会列出所有正在运行的 Java 程序的进程 ID 和它们的主类全名。有了进程 ID,你才能用接下来的其他工具。
② jstat(JVM Statistics Monitoring Tool)
- 作用:实时监控 JVM 的内存和垃圾回收(GC)情况。
- 怎么用:输入
jstat -gcutil 12345 1000(表示每隔 1000 毫秒打印一次进程 12345 的内存使用占比)。 - 看什么:你可以清晰地看到年轻代(Eden、Survivor)、老年代的内存占比。如果发现老年代占比一直是 99% 且伴随着 Full GC 次数疯狂暴涨,说明程序已经离 OOM 不远了。
③ jstack(Stack Trace for Java)
- 作用:抓取当前的线程栈快照。
- 解决什么问题:当服务器的 CPU 占用率突然飙到 100%,或者程序出现了死锁(A 线程等 B,B 线程等 A,大家都动弹不了)时,用
jstack 12345打印出所有线程正在执行的代码行数。你可以直接看到是哪一行代码把 CPU 跑满了,或者哪几个线程卡在了锁上面。
④ jmap(Memory Map for Java)
- 作用:生成堆内存快照文件(Dump 文件)。
- 解决什么问题:当程序发生 OOM 崩溃,或者你怀疑有内存泄漏时,可以用
jmap -dump:format=b,file=heap.hprof 12345把这一刻堆内存里所有的对象数据打包下载下来。然后你可以把这个heap.hprof文件导入到图形化分析工具(如 MAT 或 JProfiler)中,工具会直观地告诉你:到底是谁霸占了几个 G 的内存不撒手。
总结
到这里,我们已经完整地了解了 JVM 的核心知识体系:
- 知道了它通过字节码和各系统专属的 JVM 实现了跨平台。
- 拆解了它内部的四大核心架构和五大内存区域。
- 理解了对象如何在年轻代和老年代之间流转,并被 GC 清理。
- 搞懂了类进入内存的双亲委派机制。
- 最后掌握了后端如何面对 OOM 以及用什么排查工具进行定位与排查。
掌握了这些 JVM 的核心机制和排查手段,我们就能够在日常的后端开发中更加游刃有余地应对各种线上问题。