问题排查
内存溢出的原因
- 一次性申请太多对象->分页查询
- 内存泄漏:可达但不再使用的对象仍然占用内存->找到并释放这些对象
- 本身资源不够->jmap -heap [process id]查看堆信息
jmap是一个JDK命令行工具,用来生成heap dump。heap dump叫做堆转储快照,是一个binary file,记录了堆里面所有对象的信息。
或者,添加JVM参数-XX:+HeapDumpOnOutOfMemoryError,当程序发生OOM时,JVM会自动生成heap dump。
内存泄漏的原因
Heap Dump
添加参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/ruoke/Documents/java_web
生成一个 heap dump文件,使用jvisualmap分析。可以找到 GC root,然后看报错对应的代码行号。
|
|
常用参数
堆内存
初始堆大小(-Xms):-Xms4G
最大堆大小(-Xmx):-Xmx4G
通常设置为一样的,避免动态扩容带来的性能开销。
-Xmn512m:为新生代分配 256M 内存
新生代分配内存大一点,可以将对象尽量留在新生代,因为 minor GC 的开销比 full GC 小很多。
-XX:NewRatio=1
设置老年代与新生代内存的比值为 1
-XX:MaxMetaspaceSize=2G
设置 Metaspace 的最大大小
垃圾回收器
-XX:+UseConcMarkSweepGC
指定使用CMS垃圾回收器
-XX:MaxGCPauseMillis=50
使用G1垃圾回收器时,指定期望的最大停顿时间为50毫秒。
-XX:+PrintGCDetails -XX:+PrintGCDateStamps
打印垃圾回收的日志及时间戳
-Xloggc:/path/to/gc.log
指定垃圾回收日志文件
CPU 100%
内存不足
物理内存不足,操作系统在磁盘上划分出一块区域,叫做Swap,虚拟内存。操作系统把长时间未使用的进程暂时存储到Swap中,把剩下的物理内存留给其它进程。
频繁GC
年轻代空间设置过小/创建大量短期对象会导致频繁 minor GC。
频繁 full GC 是很严重的问题。
jstat -gc 12345 1000 5
监控进程 ID 为 12345 的 Java 进程,每隔 1 秒输出一次 GC 信息,共输出 5 次。
|
|
OC, OU代表老年代已经用的内存/总内存。
Full GC的原因:
- 一次性加载了过多大对象到内存中,比如 SQL 查询未分页,新生代存不下因此这些对象迅速进入老年期。
- 内存泄漏:文件流、数据库连接、网络连接未关闭;静态集合类(如 static List、static Map)的生命周期与 JVM 一致,如果向其中添加对象且未清理,会导致对象无法被回收;没有及时调用threadLocal.remove();
jmap -histo pid | head -n20
查看堆内存中的存活对象,并按对象占用的空间大小排序
锁竞争
业务问题
死循环/死锁/无限递归
排查过程
- top定位占用 CPU 最高的进程,再用ps -Lfp PID定位该进程中 CPU 最高的线程,把线程ID 转换为十六进制。jstack命令生成指定 Java 进程的线程快照,在里面查找十六进制的线程 ID。可以看到它的调用栈,定位到有问题的代码行号。
- jstat看 JVM 的垃圾回收次数&总耗时