news 2026/5/26 1:03:14

虾皮Java面试被问:JVM Native Memory Tracking追踪堆外内存泄漏

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
虾皮Java面试被问:JVM Native Memory Tracking追踪堆外内存泄漏

JVM Native Memory Tracking (NMT) 追踪堆外内存泄漏

📊NMT基础概念

什么是NMT?

Native Memory Tracking是HotSpot JVM内置的追踪工具,用于监控JVM内部的内存使用情况。

监控范围

bash

复制

下载

# NMT追踪的内存区域: 1. Java Heap # Java堆 2. Class # 类元数据 3. Thread # 线程栈 4. Code # JIT编译代码 5. GC # 垃圾收集器 6. Compiler # 编译器 7. Internal # 命令行解析、JVMTI等 8. Symbol # 符号表 9. Native Memory Tracking # NMT自身开销 10. Arena Chunk # 分配器竞技场 11. Test # 测试代码 12. Other # 未分类内存

🔧NMT启用与配置

启动参数配置

bash

复制

下载

# 基础启用 java -XX:NativeMemoryTracking=summary -jar app.jar # 详细模式(提供更多细节) java -XX:NativeMemoryTracking=detail -jar app.jar # 基准模式(记录初始内存使用) java -XX:NativeMemoryTracking=summary -XX:+UnlockDiagnosticVMOptions \ -XX:+PrintNMTStatistics -jar app.jar

运行时监控命令

bash

复制

下载

# 1. 获取当前内存使用情况 jcmd <pid> VM.native_memory summary # 2. 获取详细内存使用情况 jcmd <pid> VM.native_memory detail # 3. 获取按类别统计 jcmd <pid> VM.native_memory summary scale=MB # 4. 基线测量(记录当前状态作为基准) jcmd <pid> VM.native_memory baseline # 5. 对比差异(与基线对比) jcmd <pid> VM.native_memory summary.diff # 6. 详细差异对比 jcmd <pid> VM.native_memory detail.diff # 7. 按类别显示差异 jcmd <pid> VM.native_memory summary.diff scale=KB

🔍常见堆外内存泄漏场景

1.Direct ByteBuffer泄漏

java

复制

下载

// 常见的DirectByteBuffer泄漏代码 public class DirectMemoryLeak { private List<ByteBuffer> bufferList = new ArrayList<>(); public void leakMemory() { // 每次分配100MB直接内存 ByteBuffer buffer = ByteBuffer.allocateDirect(100 * 1024 * 1024); bufferList.add(buffer); // 忘记清理 } // 正确做法:需要显式释放 public void cleanDirectBuffer(ByteBuffer buffer) { if (buffer.isDirect()) { // 需要等GC回收或手动调用Cleaner ((DirectBuffer) buffer).cleaner().clean(); } } }

2.MappedByteBuffer泄漏

java

复制

下载

public class MappedBufferLeak { private List<MappedByteBuffer> mappedBuffers = new ArrayList<>(); public void mapFile(String filePath) throws IOException { RandomAccessFile file = new RandomAccessFile(filePath, "rw"); FileChannel channel = file.getChannel(); // 内存映射,会占用Native Memory MappedByteBuffer buffer = channel.map( FileChannel.MapMode.READ_WRITE, 0, channel.size() ); mappedBuffers.add(buffer); // 忘记关闭:channel.close() 和 file.close() } }

3.JNI代码内存泄漏

java

复制

下载

public class JNIMemoryLeak { static { System.loadLibrary("nativeLib"); } // Native方法可能分配内存 public native void allocateNativeMemory(long size); public native void freeNativeMemory(); public void causeLeak() { allocateNativeMemory(1024 * 1024); // 分配1MB // 忘记调用freeNativeMemory() } }

4.线程创建过多

java

复制

下载

public class ThreadLeak { private ExecutorService executor = Executors.newCachedThreadPool(); public void createThreads() { for (int i = 0; i < 10000; i++) { executor.submit(() -> { try { Thread.sleep(Long.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } }); } // 每个线程栈占用1MB(默认),10k线程占用10GB Native Memory } }

📈NMT输出解析

示例输出分析

text

复制

下载

Native Memory Tracking: Total: reserved=6643041KB, committed=397016KB - Java Heap (reserved=5070848KB, committed=317440KB) (mmap: reserved=5070848KB, committed=317440KB) - Class (reserved=1070513KB, committed=12609KB) (classes #1199) (malloc=9329KB #1525) (mmap: reserved=1067184KB, committed=3280KB) - Thread (reserved=26546KB, committed=26546KB) (thread #26) (stack: reserved=26432KB, committed=26432KB) (malloc=96KB #52) (arena=18KB #50) - Code (reserved=249632KB, committed=2592KB) (malloc=32KB #299) (mmap: reserved=249600KB, committed=2560KB) - GC (reserved=47739KB, committed=47739KB) (malloc=10423KB #117) (mmap: reserved=37316KB, committed=37316KB) - Compiler (reserved=132KB, committed=132KB) (malloc=1KB #21) (arena=131KB #3) - Internal (reserved=580KB, committed=580KB) (malloc=548KB #651) (mmap: reserved=32KB, committed=32KB) - Symbol (reserved=1525KB, committed=1525KB) (malloc=976KB #111) (arena=549KB #1) - Native Memory Tracking (reserved=161KB, committed=161KB) (malloc=99KB #1561) (tracking overhead=62KB) - Arena Chunk (reserved=175KB, committed=175KB) (malloc=175KB) - Unknown (reserved=33KB, committed=33KB) (mmap: reserved=33KB, committed=33KB)

关键指标解读

bash

复制

下载

# 重点监控区域: 1. Thread区域持续增长 → 线程泄漏 2. Internal区域异常大 → 可能DirectByteBuffer泄漏 3. GC区域过大 → GC配置问题 4. Arena Chunk异常增长 → 内存池泄漏

🚨堆外内存泄漏诊断流程

步骤1:启用NMT监控

bash

复制

下载

# 启动应用 java -XX:NativeMemoryTracking=detail \ -XX:+UnlockDiagnosticVMOptions \ -XX:+PrintNMTStatistics \ -jar application.jar # 获取进程ID jps -l # 建立基线 jcmd <pid> VM.native_memory baseline

步骤2:模拟操作并收集数据

bash

复制

下载

# 执行疑似泄漏的操作 curl http://localhost:8080/leaky-endpoint # 等待一段时间 sleep 30 # 获取差异报告 jcmd <pid> VM.native_memory summary.diff

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】​​​

步骤3:分析泄漏点

bash

复制

下载

# 详细差异分析 jcmd <pid> VM.native_memory detail.diff > nmt_diff.txt # 重点关注增长的区域 grep -A 5 "Total: " nmt_diff.txt grep "committed" nmt_diff.txt | sort -k2 -nr

步骤4:使用pmap进一步分析

bash

复制

下载

# 查看进程内存映射 pmap -x <pid> | sort -k2 -nr | head -20 # 查看anon内存段 pmap <pid> | grep anon | awk '{sum+=$2} END {print sum}'

🔧高级诊断技巧

结合其他工具分析

bash

复制

下载

# 1. 使用gdb分析Native Memory gdb -p <pid> (gdb) malloc_info 0 /tmp/malloc_info.xml # 2. 使用strace追踪系统调用 strace -f -e trace=mmap,munmap,brk -p <pid> 2>&1 | grep -v ENOENT # 3. 使用/proc文件系统 cat /proc/<pid>/maps | grep heap # 查看堆内存 cat /proc/<pid>/smaps | grep -A 15 "heap" # 详细堆信息 # 4. 监控系统内存 watch -n 1 'ps aux | grep java | grep -v grep'

自动化监控脚本

bash

复制

下载

#!/bin/bash # monitor_nmt.sh PID=$1 INTERVAL=10 OUTPUT_DIR="./nmt_logs" mkdir -p $OUTPUT_DIR while true; do TIMESTAMP=$(date +%Y%m%d_%H%M%S) # 收集NMT数据 jcmd $PID VM.native_memory detail > "$OUTPUT_DIR/nmt_detail_$TIMESTAMP.txt" jcmd $PID VM.native_memory summary > "$OUTPUT_DIR/nmt_summary_$TIMESTAMP.txt" # 收集系统内存信息 ps -p $PID -o pid,rss,vsz,pcpu,pmem,cmd >> "$OUTPUT_DIR/system_mem_$TIMESTAMP.txt" pmap $PID | tail -1 >> "$OUTPUT_DIR/pmap_$TIMESTAMP.txt" # 分析增长趋势 if [ -f "$OUTPUT_DIR/nmt_summary_previous.txt" ]; then diff "$OUTPUT_DIR/nmt_summary_previous.txt" \ "$OUTPUT_DIR/nmt_summary_$TIMESTAMP.txt" \ > "$OUTPUT_DIR/diff_$TIMESTAMP.txt" fi cp "$OUTPUT_DIR/nmt_summary_$TIMESTAMP.txt" \ "$OUTPUT_DIR/nmt_summary_previous.txt" sleep $INTERVAL done

🛠️代码层面的预防措施

1.DirectByteBuffer管理类

java

复制

下载

public class DirectMemoryManager { private static final List<ByteBuffer> BUFFERS = new ArrayList<>(); private static final Cleaner CLEANER = Cleaner.create(); public static ByteBuffer allocateDirect(long size) { ByteBuffer buffer = ByteBuffer.allocateDirect((int) size); BUFFERS.add(buffer); // 注册清理操作 CLEANER.register(buffer, () -> { if (buffer.isDirect()) { ((DirectBuffer) buffer).cleaner().clean(); } }); return buffer; } public static void cleanAll() { BUFFERS.forEach(buffer -> { if (buffer.isDirect() && buffer.capacity() > 0) { ((DirectBuffer) buffer).cleaner().clean(); } }); BUFFERS.clear(); } }

2.资源关闭模板

java

复制

下载

public class ResourceTemplate { public static void withMappedBuffer(String filePath, Consumer<MappedByteBuffer> action) { RandomAccessFile file = null; FileChannel channel = null; try { file = new RandomAccessFile(filePath, "rw"); channel = file.getChannel(); MappedByteBuffer buffer = channel.map( FileChannel.MapMode.READ_WRITE, 0, channel.size() ); action.accept(buffer); } catch (IOException e) { throw new RuntimeException(e); } finally { try { if (channel != null) channel.close(); if (file != null) file.close(); } catch (IOException e) { // 记录日志 } } } }

3.线程池监控

java

复制

下载

public class MonitoredThreadPool { private final ThreadPoolExecutor executor; private final ScheduledExecutorService monitor; public MonitoredThreadPool() { this.executor = new ThreadPoolExecutor( 10, 100, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000) ); // 监控线程池状态 this.monitor = Executors.newSingleThreadScheduledExecutor(); this.monitor.scheduleAtFixedRate(() -> { System.out.println("Thread pool stats:"); System.out.println(" Active threads: " + executor.getActiveCount()); System.out.println(" Pool size: " + executor.getPoolSize()); System.out.println(" Queue size: " + executor.getQueue().size()); // 检查Native Memory使用 try { Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec("pmap " + getPid()); // 解析输出... } catch (IOException e) { e.printStackTrace(); } }, 0, 30, TimeUnit.SECONDS); } private String getPid() { return ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; } }

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】​​​

📊监控告警配置

Prometheus监控指标

yaml

复制

下载

# NMT监控指标采集 - job_name: 'jvm_nmt' static_configs: - targets: ['localhost:12345'] metrics_path: /nmt/metrics params: pid: ['<java_pid>']

Grafana仪表板配置

json

复制

下载

{ "panels": [ { "title": "Native Memory Usage", "targets": [ { "expr": "jvm_nmt_memory_reserved{region=\"Internal\"}", "legendFormat": "Internal Memory" }, { "expr": "jvm_nmt_memory_committed{region=\"Thread\"}", "legendFormat": "Thread Stack" } ] } ] }

🚨紧急处理方案

内存泄漏紧急处理

bash

复制

下载

# 1. 立即收集证据 jcmd <pid> VM.native_memory detail > emergency_nmt_$(date +%s).txt jstack <pid> > emergency_threads_$(date +%s).txt # 2. 尝试清理DirectBuffer jmap -histo:live <pid> | grep DirectByteBuffer # 3. 如果可能,触发Full GC jcmd <pid> GC.run # 4. 限制Native Memory使用 # 重启时添加参数 -XX:MaxDirectMemorySize=512m # 限制直接内存 -XX:ThreadStackSize=256k # 减小线程栈大小 -XX:MetaspaceSize=128m # 限制元空间

📝最佳实践总结

预防措施

  1. 定期监控:生产环境开启NMT,定期收集数据

  2. 代码审查:重点审查DirectByteBuffer、MappedByteBuffer使用

  3. 资源管理:使用try-with-resources或模板方法

  4. 容量规划:合理设置JVM参数限制Native Memory

  5. 压力测试:模拟长时间运行,观察Native Memory增长

诊断要点

  1. 基线对比:使用baseline和diff功能

  2. 趋势分析:关注committed内存的持续增长

  3. 区域定位:确定是Thread、Internal还是Arena泄漏

  4. 结合工具:NMT+pmap+jstack综合分析

调优建议

bash

复制

下载

# 推荐配置 -XX:NativeMemoryTracking=summary # 生产环境开启 -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics # JVM退出时打印统计 -XX:MaxDirectMemorySize=1g # 限制直接内存 -XX:ThreadStackSize=512k # 优化线程栈大小 -XX:MetaspaceSize=256m # 设置合适的元空间 -XX:MaxMetaspaceSize=512m # 限制最大元空间

通过系统化的NMT监控和诊断,可以有效发现和解决堆外内存泄漏问题,保证Java应用的稳定运行。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/9 16:06:18

Z-Image-Turbo浏览器访问失败?端口检测与日志排查

Z-Image-Turbo浏览器访问失败&#xff1f;端口检测与日志排查 阿里通义Z-Image-Turbo WebUI图像快速生成模型 二次开发构建by科哥 运行截图 核心提示&#xff1a;当您启动 Z-Image-Turbo 后无法在浏览器中访问 http://localhost:7860&#xff0c;问题往往出在服务未正常运行、…

作者头像 李华
网站建设 2026/5/8 22:16:06

老项目升级难题?M2FP固定依赖组合避免PyTorch冲突

老项目升级难题&#xff1f;M2FP固定依赖组合避免PyTorch冲突 &#x1f4d6; 项目背景&#xff1a;多人人体解析的工程落地挑战 在计算机视觉领域&#xff0c;人体解析&#xff08;Human Parsing&#xff09; 是一项关键任务&#xff0c;旨在对图像中的人体进行像素级语义分割…

作者头像 李华
网站建设 2026/5/8 20:08:56

Apipost自动化测试使用指南

Apipost提供可视化的API自动化测试功能&#xff0c;使用Apipost研发人员可以设计、调试接口&#xff0c;测试人员可以基于同一数据源进行测试&#xff0c;Apipost 接口自动化功能在上次更新中进行了逻辑调整&#xff0c;带来更好的交互操作、更多的控制器选择&#xff0c;同时新…

作者头像 李华
网站建设 2026/5/22 23:13:08

EasyGBS卡存录像回放指南:SD卡格式化+录像计划配置两步走

最近碰到两个用户问了一模一样的问题&#xff0c;我觉得有必要跟大伙儿唠唠&#xff01;之前有个用户&#xff0c;想在国标GB28181算法算力平台EasyGBS平台看设备端的录像回放&#xff0c;结果咋都看不到。一问才知道&#xff0c;他以为设备会默认录像&#xff0c;直接在平台看…

作者头像 李华
网站建设 2026/5/15 1:13:15

工业互联网平台:MGeo统一接入企业地理位置元数据

工业互联网平台&#xff1a;MGeo统一接入企业地理位置元数据 在工业互联网的数字化转型浪潮中&#xff0c;企业跨系统、跨地域的数据整合需求日益迫切。其中&#xff0c;地理位置元数据作为连接物理世界与数字孪生体的关键桥梁&#xff0c;承担着设备定位、供应链可视化、区域…

作者头像 李华
网站建设 2026/5/22 20:15:08

JAVA源码:同城外卖跑腿与团购到店全搞定

以下是一套基于JAVA的同城外卖跑腿与团购到店一站式服务系统的源码解析与实现方案&#xff0c;该方案融合了外卖、跑腿、团购、到店服务四大核心业务模块&#xff0c;支持多商户入驻、智能派单、国际支付等完整功能&#xff1a;一、系统架构后端框架&#xff1a;采用Spring Boo…

作者头像 李华