应用异常 之 内存溢出类型
Java应用触发OutOfMemoryError的常见情况可归纳为以下六大类,每种情况对应不同的内存区域耗尽和触发条件:
一、堆内存溢出(Java Heap Space)
-
触发条件:当对象分配请求无法在Java堆中获得满足时抛出。
-
典型场景:
- 内存泄漏:对象被无意保留,无法被垃圾回收(GC)回收,持续占用内存。例如,静态集合持有对象引用、未关闭的资源(如
InputStream、Socket)、监听器未注销等。 - 数据量超过堆配置容量:一次性加载过多数据到内存,如从数据库查询大量记录未分页。
- 长时间持有大对象:如大数组、大集合未及时释放。
- 内存泄漏:对象被无意保留,无法被垃圾回收(GC)回收,持续占用内存。例如,静态集合持有对象引用、未关闭的资源(如
-
解决方案:
- 使用内存分析工具(如VisualVM、Eclipse MAT)分析堆转储文件,找出占用内存较大的对象。
- 优化代码,避免内存泄漏,如及时释放资源、清理集合对象。
- 调整JVM堆内存参数,增加初始堆大小(
-Xms)和最大堆大小(-Xmx)。
二、元空间溢出(Metaspace)
-
触发条件:类元数据占用超过Metaspace大小限制。
-
典型场景:
- 动态类生成过多:如使用CGlib代理、ASM动态生成字节码等。
- 热部署频繁:在开发环境中频繁重新部署应用,导致类加载器无法回收。
- 未合理设置Metaspace大小:默认值可能无法满足应用需求。
-
解决方案:
- 增大Metaspace大小,设置初始大小(
-XX:MetaspaceSize)和最大大小(-XX:MaxMetaspaceSize)。 - 优化类加载逻辑,减少动态生成的类数量。
- 增大Metaspace大小,设置初始大小(
三、直接内存溢出(Direct Buffer Memory)
-
触发条件:直接内存分配失败。
-
典型场景:
- NIO操作分配大量Buffer:如使用
ByteBuffer.allocateDirect()分配大量直接内存。 - 未正确释放
DirectByteBuffer对象:导致直接内存无法被回收。 XX:MaxDirectMemorySize设置过小:无法满足应用对直接内存的需求。
- NIO操作分配大量Buffer:如使用
-
解决方案:
- 增加直接内存大小,设置
-XX:MaxDirectMemorySize参数。 - 确保及时释放
DirectByteBuffer对象,避免内存泄漏。
- 增加直接内存大小,设置
四、栈空间溢出(Unable to Create New Native Thread)
-
触发条件:线程栈分配失败。
-
典型场景:
- 线程创建过多:超过操作系统或JVM的线程数量限制。
- 栈大小设置不合理:如栈大小(
-Xss)设置过大,导致可创建的线程数量减少。 - 32位JVM地址空间限制:32位JVM的地址空间有限,可能无法支持大量线程。
-
解决方案:
- 减少线程创建数量,优化线程池设计。
- 调整栈大小,设置合理的
-Xss参数值。 - 考虑升级到64位JVM,以支持更多线程。
五、GC开销限制溢出(GC Overhead Limit Exceeded)
-
触发条件:GC时间超过98%且回收内存少于2%。
-
典型场景:
- 大量对象频繁创建/回收:导致GC频繁运行,但回收效果不佳。
- 堆空间设置过小:无法满足应用对内存的需求。
- 存在内存泄漏:导致GC无法有效回收内存。
-
解决方案:
- 增加堆内存大小,设置合理的
-Xms和-Xmx参数值。 - 优化代码,减少内存消耗和对象创建频率。
- 调整GC参数,如增加
-XX:MaxHeapFreeRatio和-XX:MinHeapFreeRatio等。
- 增加堆内存大小,设置合理的
六、其他特殊溢出
- 压缩类空间溢出(Compressed Class Space):与Metaspace类似,但针对的是压缩类指针的空间。
- 代码缓存溢出(Code Cache):JIT编译代码过多,超出代码缓存大小限制。
- 数组大小超限溢出(Requested Array Size Exceeds VM Limit):尝试分配超过
Integer.MAX_VALUE大小的数组。
本文来自博客园,作者:蓝迷梦,转载请注明原文链接:https://www.cnblogs.com/hewei-blogs/articles/19370276

浙公网安备 33010602011771号