如何编程检测java.lang.OutOfMemoryError: Direct buffer memory并生成Java堆转储
Java直接内存溢出(Direct buffer memory)自动生成堆转储方案
-XX:+HeapDumpOnOutOfMemoryError参数默认仅响应堆内存溢出场景,直接内存、元空间、栈溢出等非堆OOM场景默认不会触发堆转储生成。
对应的异常日志如下:
Sep 20 23:02:55 CKM01204505062-A control-path[128819]: 2021-09-20 23:02:55.266 [] [INFO] [com.emc.bedrock.http.HttpVerticle|vert.x-eventloop-thread-3] Request ID: 2c7633a4-db9c-4ec5-b4ca-c93c47512384 completed in 4 ms. Sep 20 23:02:55 CKM01204505062-A control-path[128199]: Exception in thread "iothread-2" java.lang.OutOfMemoryError: Direct buffer memory Sep 20 23:02:55 CKM01204505062-A control-path[128199]: at java.nio.Bits.reserveMemory(Bits.java:695) Sep 20 23:02:55 CKM01204505062-A control-path[128199]: at java.nio.DirectByteBuffer.(DirectByteBuffer.java:123) Sep 20 23:02:55 CKM01204505062-A control-path[128199]: at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311) Sep 20 23:02:55 CKM01204505062-A control-path[128199]: at zmq.DecoderBase.(DecoderBase.java:35) Sep 20 23:02:55 CKM01204505062-A control-path[128199]: at zmq.V1Decoder.(V1Decoder.java:21) Sep 20 23:02:55 CKM01204505062-A control-path[128199]: at zmq.StreamEngine.newDecoder(StreamEngine.java:102) Sep 20 23:02:55 CKM01204505062-A control-path[128199]: at zmq.StreamEngine.handshake(StreamEngine.java:549) Sep 20 23:02:55 CKM01204505062-A control-path[128199]: at zmq.StreamEngine.inEvent(StreamEngine.java:278) Sep 20 23:02:55 CKM01204505062-A control-path[128199]: at zmq.IOObject.inEvent(IOObject.java:86) Sep 20 23:02:55 CKM01204505062-A control-path[128199]: at zmq.Poller.run(Poller.java:228) Sep 20 23:02:55 CKM01204505062-A control-path[128199]: at java.lang.Thread.run(Thread.java:748)
解决方案
方案1:升级JDK版本+配置JVM参数(无代码侵入首选)
JDK 8u92及以上版本新增了-XX:+HeapDumpBeforeFullGC参数,搭配-XX:MaxDirectMemorySize指定直接内存上限,同时保留原有-XX:+HeapDumpOnOutOfMemoryError参数,当直接内存达到上限触发FullGC前会自动生成堆转储文件。
参数组合示例:
-Xmx10g -XX:MaxDirectMemorySize=2g -XX:+HeapDumpOnOutOfMemoryError -XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=/var/log/java_heapdump.hprof
可额外添加-XX:+ExitOnOutOfMemoryError参数,OOM发生后自动退出进程,避免服务假死。
方案2:自定义全局异常捕获主动触发转储
在应用启动时注册全局未捕获异常处理器,当捕获到对应OOM异常时主动调用HotSpot诊断接口生成堆转储,示例代码如下:
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { if (throwable instanceof OutOfMemoryError && throwable.getMessage().contains("Direct buffer memory")) { // 主动生成堆转储 try { String dumpPath = "/var/log/direct_oom_dump_" + System.currentTimeMillis() + ".hprof"; com.sun.management.HotSpotDiagnosticMXBean bean = ManagementFactory.getPlatformMXBean(com.sun.management.HotSpotDiagnosticMXBean.class); bean.dumpHeap(dumpPath, true); } catch (Exception e) { e.printStackTrace(); } // 异常处理完成后退出进程,避免服务不可用 System.exit(-1); } });
如果使用JDK9及以上版本,需要额外添加JVM参数--add-exports jdk.management/com.sun.management.internal=ALL-UNNAMED才可以调用HotSpotDiagnosticMXBean接口。
方案3:系统日志监控触发jmap转储
编写shell脚本实时监控journalctl日志,匹配到直接内存OOM关键词时自动调用jmap生成堆转储,示例脚本片段如下:
journalctl -u 你的服务名 -f | while read line do if [[ $line == *"java.lang.OutOfMemoryError: Direct buffer memory"* ]]; then # 从日志中提取Java进程PID,可根据实际日志格式调整提取规则 PID=$(echo $line | grep -oP 'control-path\[\K[0-9]+' | head -n1) jmap -dump:format=b,file=/var/log/direct_oom_${PID}_$(date +%Y%m%d%H%M%S).hprof $PID # 可选:生成转储后停止服务避免异常扩散 systemctl stop 你的服务名 break fi done
将该脚本配置为后台服务开机自启即可实现无侵入自动捕获。
内容的提问来源于stack exchange,提问作者mandingoteck
相关产品推荐
相关产品推荐

