1. 堆内存溢出(java.lang.OutOfMemoryError: Java heap space
)
这是最常见的内存溢出问题,通常是由于对象数量过多或对象占用内存过大,导致JVM的堆内存耗尽。
常见场景
-
大量数据缓存:如将大量数据加载到内存中的缓存(如
HashMap
、List
等)。 -
未释放的对象:如静态集合中不断添加对象,但没有清理。
-
大对象:如加载大文件到内存中(如大图片、大视频等)。
示例代码
import java.util.ArrayList;
import java.util.List;
public class HeapOOMExample {
public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
while (true) {
// 不断添加1MB的字节数组
list.add(new byte[1024 * 1024]);
}
}
}
运行结果:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
如何避免?
-
合理设置堆内存大小:通过JVM参数
-Xmx
设置最大堆内存。 -
使用缓存淘汰策略:如LRU(最久未被访问)策略,避免缓存无限增长。
-
及时释放无用对象:避免静态集合中存储过多对象。
2. 方法区内存溢出(java.lang.OutOfMemoryError: Metaspace
)
方法区(Metaspace)用于存储类的元数据(如类信息、方法信息等)。如果加载的类过多,可能导致方法区内存溢出。
常见场景
-
动态生成类:如使用CGLib、ASM等工具动态生成大量类。
-
大量反射调用:频繁使用反射加载类。
-
未卸载的类加载器:如Web应用中未卸载的类加载器导致类无法回收。
示例代码
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
public class MetaspaceOOMExample {
public static void main(String[] args) {
while (true) {
// 使用CGLib动态生成类
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MetaspaceOOMExample.class);
enhancer.setUseCache(false);
enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> proxy.invokeSuper(obj, args1));
enhancer.create();
}
}
}
运行结果:Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
如何避免?
-
合理设置方法区大小:通过JVM参数
-XX:MaxMetaspaceSize
设置最大方法区内存。 -
避免动态生成过多类:如减少CGLib、ASM的使用。
-
清理类加载器:确保类加载器能够被正确卸载。
3. 栈内存溢出(java.lang.StackOverflowError
)
栈内存用于存储方法调用的栈帧。如果方法调用层次过深(如无限递归),会导致栈内存溢出。
常见场景
-
无限递归:如递归调用没有终止条件。
-
方法调用层次过深:如方法嵌套调用过多。
示例代码
public class StackOverflowExample {
public static void main(String[] args) {
infiniteRecursion();
}
public static void infiniteRecursion() {
infiniteRecursion(); // 无限递归
}
}
运行结果:Exception in thread "main" java.lang.StackOverflowError
如何避免?
-
避免无限递归:确保递归调用有终止条件。
-
增加栈内存大小:通过JVM参数
-Xss
设置栈内存大小。
4. 直接内存溢出(java.lang.OutOfMemoryError: Direct buffer memory
)
直接内存是JVM外的内存区域,通常用于NIO操作。如果直接内存分配过多,可能导致内存溢出。
常见场景
-
大量NIO操作:如使用
ByteBuffer.allocateDirect()
分配大量直接内存。 -
未释放的直接内存:如直接内存未被垃圾回收。
示例代码
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
public class DirectMemoryOOMExample {
public static void main(String[] args) {
List<ByteBuffer> list = new ArrayList<>();
while (true) {
// 不断分配直接内存
list.add(ByteBuffer.allocateDirect(1024 * 1024));
}
}
}
运行结果:Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
如何避免?
-
合理设置直接内存大小:通过JVM参数
-XX:MaxDirectMemorySize
设置最大直接内存。 -
显式释放直接内存:如调用
ByteBuffer
的clear()
方法。
5. 如何排查内存溢出问题?
-
使用JVM参数:如
-Xmx
、-XX:MaxMetaspaceSize
等,调整内存大小。 -
使用监控工具:如JVisualVM、JConsole、MAT(Memory Analyzer Tool)等,分析内存使用情况。
-
查看堆栈信息:通过
OutOfMemoryError
的堆栈信息,定位问题代码。
6. 总结
内存溢出问题通常是由于以下原因导致的:
-
堆内存不足:对象数量过多或对象占用内存过大。
-
方法区内存不足:加载的类过多。
-
栈内存不足:方法调用层次过深。
-
直接内存不足:NIO操作分配过多直接内存。
通过合理设置JVM参数、优化代码逻辑和使用监控工具,可以有效避免内存溢出问题。