visual stdio中用C#,UDP协议写一个简陋的服务器
2026/7/2 4:21:39
| 错误类型 | 触发区域 | 根本原因 | 常见场景 |
|---|---|---|---|
| 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 | 系统 | 系统内存不足 | 容器限制、物理内存不足 |
回到顶部
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(); } } } }回到顶部
java.lang.OutOfMemoryError: GC overhead limit exceededJVM花费了98%以上的时间进行GC,但只回收了不到2%的堆内存
// 场景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回到顶部
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() ) ); } }回到顶部
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); } } }回到顶部
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"; }); } } } }回到顶部
java.lang.OutOfMemoryError: Requested array size exceeds VM limit尝试创建超过JVM限制的数组
// 场景:创建超大数组 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); } } } }回到顶部
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回到顶部
# 第一步:立即保存现场 # 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回到顶部
// 使用内存敏感的数据结构 // 使用WeakHashMap、SoftReference // 及时关闭资源 // 使用try-with-resources# 生产环境推荐配置 -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# 需要监控的指标: # 1. 堆内存使用率 > 80% 告警 # 2. GC时间占比 > 20% 告警 # 3. 老年代增长速率 # 4. Metaspace使用率 # 5. 线程数增长 # 6. 直接内存使用回到顶部
| OOM类型 | 关键特征 | 优先排查点 |
|---|---|---|
| heap space | 对象太多,GC无法回收 | 大对象、内存泄露、缓存 |
| GC overhead | GC时间长,回收效率低 | 循环创建对象、字符串处理 |
| Metaspace | 类加载过多 | 动态代理、反射、热部署 |
| Direct buffer | 堆外内存不足 | Netty、NIO、MMAP |
| unable to create thread | 线程数超标 | 线程池配置、递归调用 |
| array size | 数组过大 | 大数组创建 |
| OOM Killer | 系统内存耗尽 | 容器限制、物理内存 |