Logo

深入解析Java内存溢出:排查方法与优化技巧

内存溢出是指程序申请内存时,没有足够的内存可以使用;内存溢出通常是由内存泄露引起的,常见的场景是由于有问题的代码申请完内存,却没有及时有效释放,长此以往,程序就申请不到内存,从而导致内存溢出。

一、内存溢出分类

Java 里面内存溢出主要分以下几种情况:

1.1 栈溢出

StackOverflowError

主要是指方法调用层次太深,内存不够新建栈帧,常见的场景是比如一个没有结束条件的递归。

OutOfMemoryError

主要是指同时启动的线程太多,内存不够新建线程。

1.2 堆溢出

内存溢出 - 无法申请到内存

可以使用 jstat 查看内存使用情况,检查堆参数,一般是程序申请的内存太大,比如一个特别大的 byte 数组。

内存泄漏 - 对象无法回收

可以使用 MAT/VisualVM 等工具里面的 Path to GC Roots 定位,一般是一个特大不能回收的对象。

1.3 MetaSpace 数据区溢出

主要是指动态生成大量 Class 导致 MetaSpace 数据区内存不够,常见的场景比如动态编译加载脚本代码。

1.4 本地直接内存溢出

常见的场景是程序申请一个大的直接内存。

1.5 数据超限内存溢出

常见的场景是分配的数据结构在此平台不可寻址。

二、主要排查方法

2.1 内存分析工具

内存溢出主要会通过内存分析工具类分析代码的问题,常见的分析工具如下,可以根据实际的需求选择对应的工具来进行排查,这里面用得最多的是 MAT。

产品功能MATJProfilerVisual VMjhatjmaphprof
对象关联分析、深浅堆、GC ROOT、内存泄漏检测、线程分析
离线全局分析
内存实时分配情况
OQL
内存分配堆栈、热点比例
堆外内存分析

2.2 JDK 自带命令

除了使用内存分析工具外,JDK 还提供了一些命令行工具来帮助排查内存溢出问题。以下是一些常用的命令示例:

# 监控 JVM 的内存使用情况
jstat -gcutil <pid> 250 20

# 输出虚拟机启动时传递给主类 main() 的参数,输出主类的全名
jps -ml

# 生成堆转储快照,可以使用 VisualVM/MAT 等工具分析
jmap -F -dump:live,format=b,file=dump.bin <pid>

# 显示虚拟机快照
jstack -F -l -m <pid>
jstack -l <pid> >> thread_dump.txt

# 实时查看 JVM 对象大小的排序结果,无法看到对象的具体内容
jmap -histo <pid>

2.3 参数检查

在调优内存溢出问题时,还可以通过调整 JVM 启动参数来优化内存的分配和使用。以下是一些常用的参数:

# 初始堆大小
-Xms 

# 最大堆大小
-Xmx 

# 新生代大小
-Xmn 

# 元数据区大小
XX:MaxMetaspaceSize

# Eden 与 Survivor 区的大小比值
-XX:SurvivorRatio

# 并行垃圾回收线程数
-XX:ParallelGCThreads

三、小结

本文介绍了内存溢出的分类以及一些常用的排查方法。了解不同类型的内存溢出和对应的排查方法,可以帮助开发人员更好地定位和解决内存溢出问题。另外,熟悉内存分析工具和 JDK 提供的命令行工具也是必要的,它们可以提供详细的内存使用信息和线程堆栈信息,帮助开发人员准确定位问题所在。通过不断学习和实践,开发人员可以提高对内存溢出问题的诊断和解决能力。

分享内容