问题排查

参考视频

内存溢出的原因

  1. 一次性申请太多对象->分页查询
  2. 内存泄漏:可达但不再使用的对象仍然占用内存->找到并释放这些对象
  3. 本身资源不够->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,然后看报错对应的代码行号。

1
-XX:+HeapDumpOnOutOfMemoryError -XX:ErrorFile=/logs/oom_dump/xxx.log -XX:HeapDumpPath=/logs/oom_dump/xxx.hprof

常用参数

堆内存

初始堆大小(-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%

参考B站讲解

内存不足

物理内存不足,操作系统在磁盘上划分出一块区域,叫做Swap,虚拟内存。操作系统把长时间未使用的进程暂时存储到Swap中,把剩下的物理内存留给其它进程。

频繁GC

年轻代空间设置过小/创建大量短期对象会导致频繁 minor GC。

频繁 full GC 是很严重的问题。

jstat -gc 12345 1000 5

监控进程 ID 为 12345 的 Java 进程,每隔 1 秒输出一次 GC 信息,共输出 5 次。

1
2
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
10240.0 10240.0  0.0    0.0   81920.0   65536.0   204800.0    102400.0  32000.0 31000.0 4000.0 3800.0     10    0.250     2      0.500    0.750

OC, OU代表老年代已经用的内存/总内存。

Full GC的原因:

  1. 一次性加载了过多大对象到内存中,比如 SQL 查询未分页,新生代存不下因此这些对象迅速进入老年期。
  2. 内存泄漏:文件流、数据库连接、网络连接未关闭;静态集合类(如 static List、static Map)的生命周期与 JVM 一致,如果向其中添加对象且未清理,会导致对象无法被回收;没有及时调用threadLocal.remove();

jmap -histo pid | head -n20

查看堆内存中的存活对象,并按对象占用的空间大小排序

锁竞争

业务问题

死循环/死锁/无限递归

排查过程

  1. top定位占用 CPU 最高的进程,再用ps -Lfp PID定位该进程中 CPU 最高的线程,把线程ID 转换为十六进制。jstack命令生成指定 Java 进程的线程快照,在里面查找十六进制的线程 ID。可以看到它的调用栈,定位到有问题的代码行号。
  2. jstat看 JVM 的垃圾回收次数&总耗时