这篇文章将整理一下java 堆外内存的问题的一些常用处理方法。
java 内存占用盘点 java 实际会使用内存情况如下
堆内
线程栈
方法区
二进制代码
堆外
xmx
线程数 x xss
存储类定义以及常量池
jit多层编译代码
被netty或其他堆外缓存使用
注意: 只有除了堆内,其他内存都占用的堆外内存, 他们都算整个java进程占用的内存,但无法被类似 jmap,mat 等堆内分析工具分析。
使用NMT 追踪 JVM 自用的堆外内存情况 NMT是Java7U40引入的HotSpot新特性,可用于监控JVM原生内存的使用,但比较可惜的是,目前的NMT不能监控到JVM之外或原生库分配的内存
使用方式,需要在jvm 启动时加上参数, (注意: 据官方文档 开启该功能会影响 5%-10% 的性能)
配置方式:
1 -XX:NativeMemoryTracking=summary 或者 -XX:NativeMemoryTracking=detail
查询方式:
1 jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff
e.g
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 jcmd 35740 VM.native_memory summary 35740: Native Memory Tracking: Total: reserved=3516019KB, committed=244959KB 堆内 - Java Heap (reserved=2097152KB, committed=131072KB) (mmap: reserved=2097152KB, committed=131072KB) 类数据 - Class (reserved=1062038KB, committed=10518KB) (classes #873) (malloc=5270KB #462) (mmap: reserved=1056768KB, committed=5248KB) 线程栈所占空间 - Thread (reserved=16453KB, committed=16453KB) (thread #17) (stack: reserved=16384KB, committed=16384KB) (malloc=49KB #86) (arena=20KB #33) 生成代码 - Code (reserved=249803KB, committed=2739KB) (malloc=203KB #651) (mmap: reserved=249600KB, committed=2536KB) gc 占用的空间 比如 card table - GC (reserved=82395KB, committed=75999KB) (malloc=5771KB #121) (mmap: reserved=76624KB, committed=70228KB) 编译生成代码时所占用的空间 - Compiler (reserved=138KB, committed=138KB) (malloc=7KB #41) (arena=131KB #3) 其他内部内存 command line parser, JVMTI, properties and so on - Internal (reserved=5787KB, committed=5787KB) (malloc=5755KB #2844) (mmap: reserved=32KB, committed=32KB) Symbol:保留字符串(Interned String)的引用与符号表引用放在这里 - Symbol (reserved=1962KB, committed=1962KB) (malloc=1250KB #2173) (arena=712KB #1) 本NMT 工具自己所需内存 - Native Memory Tracking (reserved=105KB, committed=105KB) (malloc=3KB #41) (tracking overhead=101KB) 文档未提及 - Arena Chunk (reserved=187KB, committed=187KB) (malloc=187KB)
各项解释的官方文档
保留内存(reserved):reserved memory 是指JVM 通过mmaped PROT_NONE 申请的虚拟地址空间,在页表中已经存在了记录(entries),保证了其他进程不会被占用,且保证了逻辑地址的连续性,能简化指针运算。
提交内存(commited):committed memory 是JVM向操做系统实际分配的内存(malloc/mmap),mmaped PROT_READ | PROT_WRITE,仍然会page faults,但是跟 reserved 不同,完全内核处理像什么也没发生一样。
其他内部内存 (Internal): 注意 Netty
使用的 ByteBuffer.allocateDirect
内部调用的 Unsafe.allocateMemory
内部其实会被 NMT 收集为 Internal 分类。
但是 MappedByteBuffers( 由 FileChannel.map 触发) 则不会被 NMT 收集到。但却会被操作系统收集。
所以,对于这个堆外内存的监控,jcmd 还不够,得上后续linux 的通用进程 内存分析工具。
这里需要注意的是:由于malloc/mmap的lazy allocation and paging机制,即使是commited的内存,也不一定会真正分配物理内存。
自用堆外内存如何追踪 使用pmap 工具查看进程内存占用 pmap -x {pid}
e.g
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [root@test-b12-java-3-52 ~]# pmap -x 140087 140087: /usr/j2sdk/jdk1.8.0_191/bin/java -Djava.util.logging.config.file=/root/apache-tomcat-xxx Address Kbytes RSS Dirty Mode Mapping 0000000000400000 4 0 0 r-x-- java 0000000000600000 4 4 4 r---- java 0000000000601000 4 4 4 rw--- java 00000000007d9000 580 508 508 rw--- [ anon ] 0000000080000000 2105236 2105044 2105044 rw--- [ anon ] ... 00007fc21b3aa000 4 4 4 rw--- [ anon ] 00007ffd9291e000 84 44 44 rw--- [ stack ] 00007ffd92972000 4 4 0 r-x-- [ anon ] ffffffffff600000 4 0 0 r-x-- [ anon ] ---------------- ------ ------ ------ total kB 16462260 3103136 3096156
可见上述进程 实际占用内存 rss 大约3G
pmap命令内部实际是读取了/proc/pid/maps和/porc/pid/smaps文件来输出。
使用strace 监控下内存分配和回收的系统调用 strace -o xxx.txt -T -tt -e mmap,munmap,mprotect -fp 12
e.g
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 140140 23:00:00.624788 mprotect(0x7fc21b3a5000, 4096, PROT_READ) = 0 <0.000205> 140140 23:00:00.625226 mprotect(0x7fc21b3a5000, 4096, PROT_READ|PROT_WRITE) = 0 <0.000168> 140140 23:00:00.625604 mprotect(0x7fc21b3a6000, 4096, PROT_NONE) = 0 <0.000189> 140140 23:00:00.688019 mprotect(0x7fc21b3a6000, 4096, PROT_READ) = 0 <0.000161> 140140 23:00:12.897296 mprotect(0x7fc21b3a5000, 4096, PROT_READ) = 0 <0.000374> 142593 23:00:12.898044 --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x7fc21b3a5880} --- 20726 23:00:12.898063 --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x7fc21b3a5480} --- 140140 23:00:12.898075 mprotect(0x7fc21b3a5000, 4096, PROT_READ|PROT_WRITE) = 0 <0.000266> 140140 23:00:12.899591 mprotect(0x7fc21b3a6000, 4096, PROT_NONE) = 0 <0.000250> 140140 23:00:12.905392 mprotect(0x7fc21b3a6000, 4096, PROT_READ) = 0 <0.000115> 140140 23:00:13.101242 mprotect(0x7fc21b3a5000, 4096, PROT_READ) = 0 <0.000272> 140140 23:00:13.101815 mprotect(0x7fc21b3a5000, 4096, PROT_READ|PROT_WRITE) = 0 <0.000235> 140140 23:00:13.102247 mprotect(0x7fc21b3a6000, 4096, PROT_NONE) = 0 <0.000149> 140140 23:00:13.158721 mprotect(0x7fc21b3a6000, 4096, PROT_READ) = 0 <0.000213> 142592 23:00:13.177198 mmap(NULL, 44793, PROT_READ, MAP_SHARED, 96, 0x84000) = 0x7fc1fc068000 <0.000400> 142592 23:00:13.181533 munmap(0x7fc1fc068000, 44793) = 0 <0.000445> 142592 23:00:13.246486 mmap(NULL, 44793, PROT_READ, MAP_SHARED, 96, 0x84000) = 0x7fc1fc068000 <0.000381>
参考 https://blog.csdn.net/qianshangding0708/article/details/100978730
https://stackoverflow.com/questions/47309859/what-is-contained-in-code-internal-sections-of-jcmd
https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr007.html
https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr022.html#BABHIFJC
http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/a9b412abe617/src/share/vm/memory/allocation.hpp#l147
https://www.baeldung.com/java-stack-heap