java 堆外内存

这篇文章将整理一下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

avatar

lelouchcr's blog