常见的OOM错误 ( OutOfMemoryError全类型详解)
2026/7/2 3:21:35 网站建设 项目流程

OOM 常见类型总览

错误类型触发区域根本原因常见场景
Java heap space堆内存对象太多,堆放不下内存泄露、大对象、流量突增
GC overhead limit exceeded堆内存GC效率低下内存泄露、堆太小、代码问题
Metaspace元空间类加载太多动态代理、反射、热部署
Direct buffer memory直接内存堆外内存不足NIO、Netty、MMAP
Unable to create new native thread栈/系统线程太多线程池配置不当、递归过深
Requested array size exceeds VM limit堆内存数组过大大数组创建
Kill process or sacrifice child系统系统内存不足容器限制、物理内存不足

回到顶部

🔍 1. Java heap space (最常见)

错误信息:

java.lang.OutOfMemoryError: Java heap space

核心原因:

对象实例占满了整个堆内存,且无法被GC回收

典型场景:

// 场景1:内存泄露 - 静态集合类持有引用 public class MemoryLeak { private static final List<byte[]> LIST = new ArrayList<>(); public void addData() { while (true) { LIST.add(new byte[1024 * 1024]); // 1MB } } } // 场景2:大对象处理 public class BigObject { public void processLargeFile() { // 一次性读取大文件到内存 byte[] fileContent = Files.readAllBytes(Paths.get("huge_file.bin")); // 10GB文件 } } // 场景3:缓存失控 public class CacheOOM { private Map<String, String> cache = new HashMap<>(); public void loadDataToCache() { // 从数据库加载大量数据到内存 List<User> users = userDao.findAll(); // 百万条记录 for (User user : users) { cache.put(user.getId(), user.serialize()); } } }

排查步骤:

# 1. 查看当前堆内存使用 jmap -heap <pid> # 2. 生成堆转储文件 jmap -dump:format=b,file=heap.hprof <pid> # 3. 实时监控GC jstat -gc <pid> 1000 # 每秒打印一次 # 4. 使用jcmd jcmd <pid> GC.heap_info

解决方案:

// 1. 合理设置JVM参数 // 生产环境示例 -Xms4g -Xmx4g // 堆内存4G,避免动态扩展 -XX:+UseG1GC // 使用G1垃圾收集器 -XX:MaxGCPauseMillis=200 // 目标暂停时间 -XX:+HeapDumpOnOutOfMemoryError // OOM时自动dump -XX:HeapDumpPath=/path/to/dumps // 2. 修复内存泄露代码 public class FixedMemoryLeak { // 使用弱引用或软引用 private static final Map<String, SoftReference<BigObject>> CACHE = new WeakHashMap<>(); // 或使用LRU缓存 private static final Map<String, BigObject> safeCache = Collections.synchronizedMap(new LinkedHashMap<String, BigObject>(16, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > 1000; // 限制大小 } }); } // 3. 分批处理大数据 public class BatchProcessor { public void processLargeData() { int batchSize = 1000; int offset = 0; while (true) { List<Data> batch = dao.findBatch(offset, batchSize); if (batch.isEmpty()) break; processBatch(batch); offset += batchSize; // 提示GC,但不是强制 if (offset % 10000 == 0) { System.gc(); } } } }

回到顶部

⏳ 2. GC overhead limit exceeded

错误信息:

java.lang.OutOfMemoryError: GC overhead limit exceeded

核心原因:

JVM花费了98%以上的时间进行GC,但只回收了不到2%的堆内存

触发条件(默认):

  • GC时间占比超过98%
  • 回收的内存不到堆的2%
  • 连续5次GC都满足上述条件

典型场景:

// 场景1:字符串拼接在循环中 public class StringOOM { public String buildHugeString() { String result = ""; for (int i = 0; i < 1000000; i++) { result += "some data "; // 每次创建新StringBuilder和String } return result; } } // 场景2:频繁创建临时对象 public class TempObjectOOM { public void process() { while (true) { // 每次循环都创建新对象,快速进入老年代 byte[] buffer = new byte[1024 * 1024]; // 1MB // 但buffer很快失去引用,成为垃圾 } } }

排查方法:

# 1. 查看GC详细日志 java -Xlog:gc*,gc+heap=debug:file=gc.log -Xmx512m YourApp # 2. 使用VisualGC或JConsole监控 # 3. 分析GC日志文件

解决方案:

// 1. 优化JVM参数 -Xmx2g -Xms2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1ReservePercent=15 -XX:InitiatingHeapOccupancyPercent=35 // 2. 代码优化 public class OptimizedStringBuilder { public String buildHugeString() { StringBuilder sb = new StringBuilder(10000000); // 预分配容量 for (int i = 0; i < 1000000; i++) { sb.append("some data "); } return sb.toString(); } } // 3. 对象复用 public class ObjectPool { private static final ThreadLocal<ByteBuffer> bufferPool = ThreadLocal.withInitial(() -> ByteBuffer.allocate(8192)); public void process() { ByteBuffer buffer = bufferPool.get(); buffer.clear(); // 使用buffer } } // 4. 关闭GC overhead限制(不推荐,临时方案) -XX:-UseGCOverheadLimit

回到顶部

🧠 3. Metaspace (Java 8+)

错误信息:

java.lang.OutOfMemoryError: Metaspace

核心原因:

加载的类太多,元空间不足

典型场景:

// 场景1:动态代理大量生成类 public class DynamicProxyOOM { public void createManyProxies() { for (int i = 0; i < 1000000; i++) { MyInterface proxy = (MyInterface) Proxy.newProxyInstance( getClass().getClassLoader(), new Class[]{MyInterface.class}, new MyInvocationHandler() ); // 每个代理都会生成新类 } } } // 场景2:热部署频繁 // 应用频繁重启,旧类未卸载 // 场景3:大量使用反射 public class ReflectionOOM { public void loadManyClasses() throws Exception { for (int i = 0; i < 10000; i++) { Class<?> clazz = Class.forName("com.example.Class" + i); // 每个类都加载到Metaspace } } }

排查方法:

# 1. 查看元空间使用情况 jstat -gc <pid> | grep MC # MC: 元空间容量 # MU: 元空间已使用 # 2. 查看加载的类 jcmd <pid> GC.class_stats # 3. dump类加载信息 -XX:+TraceClassLoading -XX:+TraceClassUnloading

解决方案:

// 1. 调整Metaspace参数 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:CompressedClassSpaceSize=256m // 2. 使用不同的ClassLoader public class CustomClassLoader extends URLClassLoader { public CustomClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); } // 需要时创建新的ClassLoader实例 // 不需要时,整个ClassLoader可以被回收 } // 3. 限制动态代理 public class LimitedProxyCreator { private static final Map<String, Object> PROXY_CACHE = new ConcurrentHashMap<>(); public MyInterface getProxy(String key) { return (MyInterface) PROXY_CACHE.computeIfAbsent(key, k -> Proxy.newProxyInstance( getClass().getClassLoader(), new Class[]{MyInterface.class}, new MyInvocationHandler() ) ); } }

回到顶部

💾 4. Direct buffer memory

错误信息:

java.lang.OutOfMemoryError: Direct buffer memory

核心原因:

堆外内存(Direct Buffer)耗尽

典型场景:

// 场景1:Netty使用不当 public class NettyOOM { public void startServer() { // Netty默认使用堆外内存 ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { // 如果不释放ByteBuf,会导致堆外内存泄露 ch.pipeline().addLast(new MyHandler()); } }); } } // 场景2:大量使用ByteBuffer.allocateDirect public class DirectBufferOOM { public void allocateBuffers() { List<ByteBuffer> buffers = new ArrayList<>(); while (true) { // 每个Buffer 1MB,但不被GC管理 buffers.add(ByteBuffer.allocateDirect(1024 * 1024)); } } }

排查方法:

# 1. 查看直接内存使用 jcmd <pid> VM.native_memory summary scale=MB # 2. 使用NMT(Native Memory Tracking) -XX:NativeMemoryTracking=summary jcmd <pid> VM.native_memory detail # 3. 查看BufferPool jcmd <pid> ManagementAgent.jmx_invoke sun.nio.ch.BufTracker getDirectBufferPoolCount

解决方案:

// 1. 限制直接内存大小 -XX:MaxDirectMemorySize=256m // 2. Netty内存泄露检测 // 添加内存泄露检测 .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator()) // 启用泄露检测 ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID); // 3. 正确释放资源 public class SafeDirectBuffer { public void useDirectBuffer() { ByteBuffer buffer = ByteBuffer.allocateDirect(1024); try { // 使用buffer buffer.put("data".getBytes()); } finally { // 重要:手动清理 if (buffer.isDirect()) { ((DirectBuffer) buffer).cleaner().clean(); } } } } // 4. 使用池化ByteBuf public class PooledBufferExample { private final ByteBufPool bufferPool = new ByteBufPool(); public void process() { ByteBuf buf = bufferPool.borrowBuffer(); try { // 使用buf } finally { bufferPool.returnBuffer(buf); } } }

回到顶部

🧵 5. Unable to create new native thread

错误信息:

java.lang.OutOfMemoryError: unable to create new native thread

核心原因:

创建的线程数超过系统限制

典型场景:

// 场景1:递归创建线程 public class RecursiveThreadOOM { public void createThreads() { while (true) { new Thread(() -> { try { Thread.sleep(Long.MAX_VALUE); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); } } } // 场景2:线程池配置不当 public class ThreadPoolOOM { public void misuseExecutor() { // 错误:使用无界队列,线程数会一直增长 ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, // corePoolSize Integer.MAX_VALUE, // 最大线程数太大 60L, TimeUnit.SECONDS, new SynchronousQueue<>() // 队列太小 ); } }

排查方法:

# 1. 查看系统线程限制 ulimit -u cat /proc/sys/kernel/threads-max # 2. 查看Java进程线程数 pstree -p <pid> | wc -l jstack <pid> | grep "java.lang.Thread.State" | wc -l # 3. 查看每个线程的栈大小 jinfo -flag ThreadStackSize <pid>

解决方案:

// 1. 调整系统参数 // Linux系统 echo 10000 > /proc/sys/kernel/threads-max ulimit -u 10000 // 2. 调整JVM参数 -Xss256k # 减小线程栈大小 -XX:VMThreadStackSize=256 // 3. 合理使用线程池 public class SafeThreadPool { private final ExecutorService executor = new ThreadPoolExecutor( 10, // 核心线程 100, // 最大线程 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000), // 有界队列 new ThreadFactoryBuilder() .setNameFormat("worker-%d") .build(), new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 ); // 4. 使用虚拟线程(Java 19+) public void useVirtualThreads() { try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { for (int i = 0; i < 100000; i++) { executor.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); return "Done"; }); } } } }

回到顶部

📈 6. Requested array size exceeds VM limit

错误信息:

java.lang.OutOfMemoryError: Requested array size exceeds VM limit

核心原因:

尝试创建超过JVM限制的数组

最大限制:

  • 32位JVM:约2^31-1 = 2,147,483,647 元素
  • 64位JVM:约2^31-1(受堆大小限制)

典型场景:

// 场景:创建超大数组 public class HugeArrayOOM { public void createHugeArray() { // 尝试创建20亿个元素的int数组 // 20亿 * 4字节 ≈ 8GB int[] hugeArray = new int[2_000_000_000]; } }

解决方案:

// 1. 分批处理 public class BatchArrayProcessor { public void processLargeData(long totalSize) { int batchSize = 1000000; // 每批100万 int batches = (int) Math.ceil((double) totalSize / batchSize); for (int i = 0; i < batches; i++) { int currentSize = Math.min(batchSize, (int)(totalSize - i * batchSize)); int[] batch = new int[currentSize]; processBatch(batch); } } } // 2. 使用稀疏数组 public class SparseArrayExample { private Map<Integer, Integer> sparseArray = new HashMap<>(); public void set(int index, int value) { if (value != 0) { // 只存储非零值 sparseArray.put(index, value); } } public int get(int index) { return sparseArray.getOrDefault(index, 0); } } // 3. 使用内存映射文件 public class MappedFileArray { public void processLargeFile(String filePath, long arraySize) throws IOException { try (RandomAccessFile file = new RandomAccessFile(filePath, "rw"); FileChannel channel = file.getChannel()) { MappedByteBuffer buffer = channel.map( FileChannel.MapMode.READ_WRITE, 0, arraySize * 4 ); IntBuffer intBuffer = buffer.asIntBuffer(); // 像操作数组一样操作intBuffer for (int i = 0; i < arraySize; i++) { intBuffer.put(i, i * 2); } } } }

回到顶部

🔧 7. Kill process or sacrifice child

错误信息(Linux OOM Killer):

Out of memory: Kill process [pid] (java) score [score] or sacrifice child

核心原因:

系统物理内存耗尽,Linux OOM Killer终止进程

排查方法:

# 查看OOM Killer日志 dmesg | grep -i "out of memory" dmesg | grep -i "killed process" # 查看系统内存 free -h cat /proc/meminfo # 查看进程内存 ps aux --sort=-%mem | head -20

解决方案:

// 1. 调整JVM内存参数 // 不要设置过大,预留系统内存 -Xmx8g # 8GB堆内存 -Xms8g -XX:MaxMetaspaceSize=512m -XX:MaxDirectMemorySize=256m -XX:ReservedCodeCacheSize=256m // 2. 使用容器时设置内存限制 # Docker示例 docker run -m 10g --memory-reservation=8g your-java-app # Kubernetes示例 resources: limits: memory: "10Gi" requests: memory: "8Gi" // 3. 使用Native Memory Tracking监控 -XX:NativeMemoryTracking=detail jcmd <pid> VM.native_memory baseline jcmd <pid> VM.native_memory detail.diff // 4. 调整系统OOM Killer参数 echo 100 > /proc/sys/vm/overcommit_memory echo 1 > /proc/sys/vm/overcommit_ratio

回到顶部

🎯 实战排查流程

当发生OOM时,按此流程排查:

# 第一步:立即保存现场 # 1. 保存错误日志 # 2. 生成堆转储 jmap -dump:live,format=b,file=heap.hprof <pid> # 第二步:分析内存使用 # 1. 查看堆内存分布 jmap -histo:live <pid> | head -20 # 2. 查看GC情况 jstat -gcutil <pid> 1000 10 # 3. 查看线程状态 jstack <pid> > thread.dump # 第三步:使用分析工具 # 1. Eclipse MAT # 2. VisualVM # 3. JProfiler # 4. YourKit # 第四步:复现和监控 # 1. 设置监控参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./java_pid<pid>.hprof -XX:OnOutOfMemoryError="kill -3 %p" -Xlog:gc*,gc+heap=debug:file=gc_%t.log

回到顶部

📊 预防策略

1. 代码层面

// 使用内存敏感的数据结构 // 使用WeakHashMap、SoftReference // 及时关闭资源 // 使用try-with-resources

2. JVM参数优化

# 生产环境推荐配置 -Xms4g -Xmx4g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:MaxDirectMemorySize=256m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:InitiatingHeapOccupancyPercent=35 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -Xlog:gc*,gc+heap=debug:file=gc.log

3. 监控告警

# 需要监控的指标: # 1. 堆内存使用率 > 80% 告警 # 2. GC时间占比 > 20% 告警 # 3. 老年代增长速率 # 4. Metaspace使用率 # 5. 线程数增长 # 6. 直接内存使用

回到顶部

💎 总结

OOM类型关键特征优先排查点
heap space对象太多,GC无法回收大对象、内存泄露、缓存
GC overheadGC时间长,回收效率低循环创建对象、字符串处理
Metaspace类加载过多动态代理、反射、热部署
Direct buffer堆外内存不足Netty、NIO、MMAP
unable to create thread线程数超标线程池配置、递归调用
array size数组过大大数组创建
OOM Killer系统内存耗尽容器限制、物理内存

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询