JVM 内存溢出 详解
内存溢出
内存溢出指的是内存中某一块区域的使用量超过了允许使用的最大值,从而使用内存时因空间不足而失败,虚拟机一般会抛出指定的错误。
在Java虚拟机中,只有程序计数器不会出现内存溢出的情况,因为每个线程的程序计数器只保存一个固定长度的地址。
堆内存溢出
(java.lang.OutOfMemoryError: Java heap space
)
堆内存溢出指的是在堆上分配的对象空间超过了堆的最大大小,从而导致的内存溢出。堆的最大大小使用-Xmx参数进行设置,如-Xmx10m代表最大堆内存大小为10m。
Java堆用于储存对象实例,我们只要不断地创建对象,并且保证GC Roots到对象之间有可达路径 来避免垃圾回收机制清除这些对象,那么随着对象数量的增加,总容量触及最大堆的容量限制后就会 产生内存溢出异常。
栈内存溢出
(java.lang.OutOfMemoryError: unable to create new native thread
)
栈内存溢出指的是所有栈帧空间的占用内存超过了最大值,最大值使用-Xss进行设置,比如-Xss256k代表所有栈帧占用内存大小加起来不能超过256k。
方法区内存溢出
(java.lang.OutOfMemoryError: Metaspace
/ PermGen space
)
方法区内存溢出指的是方法区中存放的内容比如类的元信息超过了方法区内存的最大值,JDK7及之前版本方法区使用永久代(-XX:MaxPermSize=值)来实现,JDK8及之后使用元空间(-XX:MaxMetaspaceSize=值)来实现。
原因:加载的类过多或动态生成类(如反射、CGLIB),超出元空间限制(-XX:MaxMetaspaceSize
)。
import javassist.ClassPool;public class MetaspaceOOM {public static void main(String[] args) throws Exception {ClassPool cp = ClassPool.getDefault();for (int i = 0; i < 100000; i++) {// 动态生成类Class<?> clazz = cp.makeClass("MetaspaceOOM" + i).toClass();}}
}
直接内存溢出
(java.lang.OutOfMemoryError: Direct buffer memory
)
直接内存溢出指的是申请的直接内存空间大小超过了最大值,使用 -XX:MaxDirectMemorySize=值 设置最大值。溢出之后会抛出OutOfMemoryError:
总结:
OOM 类型 | 触发原因 | 解决方案 |
---|---|---|
堆内存溢出 | 对象过多/内存泄漏 | 调整 -Xmx ,优化代码 |
元空间溢出 | 动态类过多 | 限制 -XX:MaxMetaspaceSize |
栈溢出(线程数过多) | 线程数超出系统限制 | 使用线程池,调整 -Xss |
直接内存溢出 | NIO 分配过多直接内存 | 调整 -XX:MaxDirectMemorySize |
- 通过
jstat
、jmap
、jstack
监控内存使用。 - 生成堆转储文件(
-XX:+HeapDumpOnOutOfMemoryError
),用 MAT 分析内存泄漏。 - 避免过度依赖反射、动态代理等易触发元空间问题的技术。
如何避免 OOM?
1. 堆内存溢出
• 诊断工具:
• 使用 jvisualvm
、MAT
(Memory Analyzer Tool)分析堆转储(-XX:+HeapDumpOnOutOfMemoryError
)。
• 检查是否有内存泄漏(对象被意外长期引用)。
• 优化方法:
• 调整堆大小(-Xmx
和 -Xms
)。
• 优化代码,及时释放无用对象(如清理集合、关闭资源)。
• 避免创建超大对象(如大数组)。
2. 元空间溢出
• 诊断工具:
• 使用 jstat -gcmetacapacity
监控元空间使用情况。
• 检查动态生成类的代码(如反射、动态代理)。
• 优化方法:
• 限制元空间大小(-XX:MaxMetaspaceSize=256m
)。
• 减少动态类生成(如缓存反射生成的类)。
3. 栈溢出(线程数过多)
• 诊断工具:
• 检查线程数(ps -eLf | grep java
)。
• 分析线程栈(jstack
)。
• 优化方法:
• 减少线程数(使用线程池)。
• 调整线程栈大小(-Xss256k
)。
4. 直接内存溢出
• 诊断工具:
• 监控 java.nio.Bits
的 reservedMemory
(NIO 内存使用)。
• 优化方法:
• 显式释放直接内存(调用 ((DirectBuffer) buffer).cleaner().clean()
)。
• 调整 -XX:MaxDirectMemorySize
。