【性能优化】Java 堆外内存的实战分析
本文在 Kubernetes 部署场景下,分析容器内 Java进程的堆外内存
1.堆外内存的分析思路
采集 JVM 管理的内存区间和 Java 进程的所有内存区间,对比分析 Java 进程内非 JVM 管理的内存区间即堆外内存,接着分析这些内存的详情。首先利用 JVM 的 NMT(Native Memory Tracking)获取 JVM 管理的所有内存区间,然后通过 Linux 调试及运维工具 pmap 查看 Java 进程所有的内存映像信息,最后使用 GDB 分析非 JVM 管理的内存详情。
问题:什么是 JVM 的堆外内存?
答:堆外内存是指分配在 JVM 堆外的内存区域,即 JVM 不管理的内存区域。
注意:
① 尽管 JVM 通过-XX:MetaspaceSize
、-XX:MaxMetaspaceSize
设置 Metaspace 大小,但 Metaspace 也属于堆外内存
② 尽管 JVM 通过-XX:MaxDirectMemorySize
设置 Direct Memory 大小,但 Direct Memory 也属于堆外内存
2.利用 NMT 获取 JVM 管理的所有内存区间
通过 JVM 的 NMT(Native Memory Tracking)查询 JVM 管理的所有内存区间
步骤 1:设置 Java 进程的启动参数 -XX:NativeMemoryTracking=detail
-XX:NativeMemoryTracking=detail
# off: 默认关闭
# summary: 只统计各个分类的内存使用情况.
# detail: Collect memory usage by individual call sites.
步骤 2:利用 jdk 命令,打印 JVM 管理的所有内存区间
#进入容器,查询java的pid
$kubectl get pod -ntest |grep application
application-job-xx-d54cc78b9-l5xtb 1/1 Running 0 5m
application-job-xx-taskmanager-1-1 1/1 Running 0 3m
$kubectl exec -ntest -it application-job-xx-d54cc78b9-l5xtb bash
$ps -ef |grep java |grep NativeMemoryTracking
paas 477 1 19 07:50 ? 00:00:35 /usr/local/jre1.8.0_322//bin/java -XX:NativeMemoryTracking=detail -Xmx400556031 -Xms400556031 -XX:MaxMetaspaceSize=125829120 ...省略
# 进入目录 */jdk/bin
$./jcmd 477 VM.native_memory detail scale=MB > native_memory.txt
# native_memory.txt的内容如下
477:
Native Memory Tracking:
Total: reserved=1959MB, committed=725MB
- Java Heap (reserved=382MB, committed=382MB)
(mmap: reserved=382MB, committed=382MB)
- Class (reserved=1116MB, committed=104MB)
(classes #18353)
(malloc=2MB #15599)
(mmap: reserved=1114MB, committed=103MB)
- Thread (reserved=96MB, committed=96MB)
(thread #96)
(stack: reserved=95MB, committed=95MB)
- Code (reserved=248MB, committed=25MB)
(malloc=4MB #8021)
(mmap: reserved=244MB, committed=21MB)
- GC (reserved=1MB, committed=1MB)
(mmap: reserved=1MB, committed=1MB)
- Internal (reserved=88MB, committed=88MB)
(malloc=88MB #23546)
- Symbol (reserved=24MB, committed=24MB)
(malloc=22MB #207856)
(arena=2MB #1)
- Native Memory Tracking (reserved=4MB, committed=4MB)
(tracking overhead=4MB)
Virtual memory map:
[0x00000000e8200000 - 0x0000000100000000] reserved 382MB for Java Heap from
[0x00007f5f03974a32] ReservedSpace::initialize(unsigned long, unsigned long, bool, char*, unsigned long, bool)+0xa2
[0x00007f5f039751c1] ReservedHeapSpace::ReservedHeapSpace(unsigned long, unsigned long, bool, char*)+0x61
[0x00007f5f0393f7a2] Universe::reserve_heap(unsigned long, unsigned long)+0x72
[0x00007f5f0345c508] GenCollectedHeap::allocate(unsigned long, unsigned long*, int*, ReservedSpace*)+0x198
[0x00000000f0150000 - 0x0000000100000000] committed 255MB from
[0x00007f5f0397585c] VirtualSpace::expand_by(unsigned long, bool)+0x13c
[0x00007f5f03472fc3] Generation::Generation(ReservedSpace, unsigned long, int)+0xc3
[0x00007f5f034733ff] CardGeneration::CardGeneration(ReservedSpace, unsigned long, int, GenRemSet*)+0x5f
[0x00007f5f03918865] TenuredGeneration::TenuredGeneration(ReservedSpace, unsigned long, int, GenRemSet*)+0x75
[0x00000000e8200000 - 0x00000000f0150000] committed 127MB from
[0x00007f5f0397585c] VirtualSpace::expand_by(unsigned long, bool)+0x13c
[0x00007f5f03472fc3] Generation::Generation(ReservedSpace, unsigned long, int)+0xc3
[0x00007f5f0336b529] DefNewGeneration::DefNewGeneration(ReservedSpace, unsigned long, int, char const*)+0x59
[0x00007f5f034748f3] GenerationSpec::init(ReservedSpace, int, GenRemSet*)+0x423
[0x0000000100000000 - 0x0000000140000000] reserved 1024MB for Class from
[0x00007f5f03974a32] ReservedSpace::initialize(unsigned long, unsigned long, bool, char*, unsigned long, bool)+0xa2
[0x00007f5f03974c44] ReservedSpace::ReservedSpace(unsigned long, unsigned long, bool, char*, unsigned long)+0x24
[0x00007f5f0375011b] Metaspace::allocate_metaspace_compressed_klass_ptrs(char*, unsigned char*)+0x4b
[0x00007f5f037509e4] Metaspace::global_initialize()+0x4c4
[0x0000000100000000 - 0x0000000100dc0000] committed 14MB from
[0x00007f5f0397585c] VirtualSpace::expand_by(unsigned long, bool)+0x13c
[0x00007f5f03749ebf] VirtualSpaceList::expand_node_by(VirtualSpaceNode*, unsigned long, unsigned long)+0x4f
[0x00007f5f0374ec49] VirtualSpaceList::expand_by(unsigned long, unsigned long)+0x89
[0x00007f5f0374eda2] VirtualSpaceList::get_new_chunk(unsigned long, unsigned long)+0xa2
...省略
3.利用 PMAP 获取进程内的所有内存区间
通过 Linux 调试及运维工具 pmap - report memory map of a process,查看进程的内存映像信息,即需要打印进程内所有内存块地址区间
步骤 1:利用 pmap
打印进程内所有的内存地址区间
#进入容器,查询java的pid
$kubectl get pod -ntest |grep application
application-job-xx-d54cc78b9-l5xtb 1/1 Running 0 5m
application-job-xx-taskmanager-1-1 1/1 Running 0 3m
$kubectl exec -ntest -it application-job-xx-d54cc78b9-l5xtb bash
$ps -ef |grep java |grep NativeMemoryTracking
paas 477 1 19 07:50 ? 00:00:35 /usr/local/jre1.8.0_322//bin/java -XX:NativeMemoryTracking=detail -Xmx400556031 -Xms400556031 -XX:MaxMetaspaceSize=125829120 ...省略
#利用pmap
$pmap -x 477 | sort -n -k3 > process_memory_address.txt
# process_memory_address.txt的内容如下
---------------- ------- ------- -------
00007f5eaf6fe000 3064 2164 2164 rw--- [ anon ]
00007f5ec6e9d000 3064 2164 2164 rw--- [ anon ]
00007f5ea94a7000 3064 2208 2208 rw--- [ anon ]
00007f5eadde9000 3064 2208 2208 rw--- [ anon ]
00007f5f004e5000 2488 2488 2488 rw--- [ anon ]
00007f5ecc000000 2708 2708 2708 rw--- [ anon ]
00007f5ee8000000 3328 3328 3328 rw--- [ anon ]
00007f5ec0000000 3968 3840 3840 rw--- [ anon ]
00007f5eb4000000 4232 3928 3928 rw--- [ anon ]
000055a992a85000 4640 4544 4544 rw--- [ anon ]
00007f5eec0ac000 6520 6520 0 r--s- flink-dist_2.11-1.13.6.jar
00007f5ebc000000 6952 6928 6928 rw--- [ anon ]
00007f5f0302d000 9684 9036 0 r-x-- libjvm.so
00007f5eec70a000 9176 9176 0 r--s- WordCount.jar
00007f5f01b8f000 9188 9184 9184 rw--- [ anon ]
00007f5ed0000000 12748 12748 12748 rw--- [ anon ]
00007f5efc000000 22508 21820 21820 rw--- [ anon ]
00007f5edc000000 32016 26808 26808 rw--- [ anon ]
00007f5eed000000 27776 27748 27748 rwx-- [ anon ]
00000000e8200000 405248 217592 217592 rw--- [ anon ]
total kB 3019696 480300 445160
...省略
cat /proc/477/smaps
可以打印详细的内存地址,例如
$ cat /proc/477/smaps
e8200000-100dc0000 rw-p 00000000 00:00 0
Size: 405248 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 217588 kB
Pss: 217588 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 217588 kB
Referenced: 217420 kB
Anonymous: 217588 kB
LazyFree: 0 kB
AnonHugePages: 8192 kB
ShmemPmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
THPeligible: 1
VmFlags: rd wr mr mw me ac sd
...省略
步骤 2:对比上述 2 中打印的全部 JVM 所管理的内存区间,确定比较大的非 JVM 管理的内存地址区间
# 从process_memory_address.txt确定内存地址 00007f5ea97b5000 比较大
00007f5ebc000000 6952 6928 6928 rw--- [ anon ]
# 获取连续的内存地址
$pmap -x 477 |grep -A 1 00007f5ea97b5000
00007f5ea97b5000 6952 6928 6928 rw--- [ anon ]
00007f5ea99b4000 2044 24 0 ----- [ anon ]
4.利用 GDB 分析非 JVM 管理的内存详情
GDB 全称“GNU symbolic debugger”,是 Linux 下常用的程序调试器。分析非 JVM 管理的内存详情,主要使用了 dump 内存的功能。利用工具 nsenter 和 容器 Pid 进入容器进程的 namespace,最后通过 gdb dump 内存和 hexdump 查看 bin 二进制文件
步骤 1:进入容器的命名空间
# 查询容器名称
$kubectl get pod -ntest |grep application
application-job-xx-d54cc78b9-l5xtb 1/1 Running 0 4h15m
application-job-xx-taskmanager-1-1 1/1 Running 0 5h13m
# 查看容器id
$docker ps |grep application-job-xx-d54cc78b9-l5xtb
5456099bd648 1bd14b7f018a "/opt/container/flink/scri…" 4 hours ago Up 4 hours k8s_flink-main-container_application-job-xx-d54cc78b9-l5xtb_flink_6ae62c3f-e5e9-4810-9ade-2dd908f2a37a_0
99d3788c5285 192.168.31.37:5000/pause:3.2 "/pause" 4 hours ago Up 4 hours k8s_POD_application-job-xx-d54cc78b9-l5xtb_flink_6ae62c3f-e5e9-4810-9ade-2dd908f2a37a_0
# 查看虚拟机上目标容器的Pid
$docker inspect 5456099bd648 |grep Pid
"Pid": 6789,
"PidMode": "",
"PidsLimit": 0,
# 进入目标Pid的命名空间
$nsenter -t 6789
步骤 2:利用 gdb dump 目标内存地址的详情
# 查看虚拟机上目标容器的进程id
$ps -ef |grep application-job-xx-d54cc78b9-l5xtb
service 8028 6789 2 02:55 ? 00:06:38 /usr/local/jre1.8.0_322//bin/java -XX:NativeMemoryTracking=detail -Xmx400556031 -Xms400556031 -XX:MaxMetaspaceSize=125829120 ...省略
root 125820 124355 0 07:25 pts/9 00:00:00 grep --color=auto application-job-xx-d54cc78b9-l5xtb
# 进入gdb dum
$gdb dump
GNU gdb (GDB) EulerOS 8.3.1-12.eulerosv2r9
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-openEuler-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
dump: No such file or directory.
# 绑定进程id
$(gdb) attach 8028
Attaching to process 8028
[New LWP 8031]
[New LWP 8032]
[New LWP 8033]
...省略
...省略
Missing separate debuginfo for target:/usr/lib64/libnss_myhostname.so.2
Try: dnf --enablerepo='*debug*' install /usr/lib/debug/.build-id/f8/86f51fb3550c00854193e611c36670c74759d7.debug
warning: Target and debugger are in different PID namespaces; thread lists and other data are likely unreliable. Connect to gdbserver inside the container.
warning: Expected absolute pathname for libpthread in the inferior, but got target:/usr/lib64/libpthread.so.0.
warning: Unable to find libthread_db matching inferior's thread library, thread debugging will not be available.
0x00007fdf244802dd in ?? () from target:/usr/lib64/libpthread.so.0
#dump目标内存地址的详情
(gdb) dump memory /tmp/dump.bin 0x00007f5ea97b5000 00007f5ea99b4000
利用 hexdump 查看 gdb dump 的 bin 文件
$hexdump -C /tmp/dump.bin
00000000 e0 aa ff ff 78 01 00 00 00 41 0e 10 86 02 45 0d |....x....A....E.|
00000010 06 45 8f 03 45 8e 04 4b 8d 05 49 8c 06 44 83 07 |.E..E..K..I..D..|
00000020 03 14 01 0a 0c 07 08 44 0b 00 00 00 00 00 00 00 |.......D........|
00000030 1c 00 00 00 94 16 00 00 28 ac ff ff 19 00 00 00 |........(.......|
00000040 00 41 0e 10 86 02 4e 0d 06 45 0c 07 08 00 00 00 |.A....N..E......|
00000050 2c 00 00 00 b4 16 00 00 28 ac ff ff 64 00 00 00 |,.......(...d...|
00000060 00 41 0e 10 86 02 46 0d 06 4b 8c 06 83 07 57 8f |.A....F..K....W.|
00000070 03 8e 04 8d 05 76 0c 07 08 00 00 00 00 00 00 00 |.....v..........|
00000080 34 00 00 00 e4 16 00 00 68 ac ff ff 34 01 00 00 |4.......h...4...|
00000090 00 41 0e 10 86 02 43 0d 06 4b 8c 04 83 05 52 8d |.A....C..K....R.|
000000a0 03 02 8d 0a 0c 07 08 42 0b 02 62 0a 0c 07 08 46 |.......B..b....F|
000000b0 0b 00 00 00 00 00 00 00 24 00 00 00 1c 17 00 00 |........$.......|
000000c0 70 ad ff ff 2b 00 00 00 00 41 0e 10 86 02 47 0d |p...+....A....G.|
000000d0 06 4f 0a 0c 07 08 49 0b 48 0c 07 08 00 00 00 00 |.O....I.H.......|
000000e0 2c 00 00 00 44 17 00 00 78 ad ff ff 48 00 00 00 |,...D...x...H...|
000000f0 00 43 0e 10 86 02 49 0d 06 58 0a 0c 07 08 44 0b |.C....I..X....D.|
00000100 48 0a 0c 07 08 48 0b 4f 0c 07 08 00 00 00 00 00 |H....H.O........|
00000110 2c 00 00 00 74 17 00 00 98 ad ff ff 61 01 00 00 |,...t.......a...|
00000120 00 41 0e 10 86 02 4d 0d 06 45 8f 03 49 8e 04 49 |.A....M..E..I..I|
00000130 8d 05 45 8c 06 7f 83 07 02 e1 0a 0c 07 08 41 0b |..E...........A.|
00000140 1c 00 00 00 a4 17 00 00 d8 ae ff ff 15 00 00 00 |................|
00000150 00 41 0e 10 86 02 4e 0d 06 41 0c 07 08 00 00 00 |.A....N..A......|
00000160 24 00 00 00 c4 17 00 00 d8 ae ff ff 58 00 00 00 |$...........X...|
00000170 00 41 0e 10 86 02 46 0d 06 4b 8c 05 83 06 55 8e |.A....F..K....U.|
00000180 03 8d 04 6c 0c 07 08 00 1c 00 00 00 ec 17 00 00 |...l............|
00000190 10 af ff ff 17 00 00 00 00 41 0e 10 86 02 43 0d |.........A....C.|
000001a0 06 52 0c 07 08 00 00 00 2c 00 00 00 0c 18 00 00 |.R......,.......|
000001b0 10 af ff ff 36 01 00 00 00 41 0e 10 86 02 43 0d |....6....A....C.|
000001c0 06 4b 8c 06 83 07 5c 8f 03 8e 04 8d 05 75 0a 0c |.K....\......u..|
000001d0 07 08 48 0b 00 00 00 00 2c 00 00 00 3c 18 00 00 |..H.....,...<...|
000001e0 20 b0 ff ff cd 00 00 00 00 41 0e 10 86 02 46 0d | ........A....F.|
000001f0 06 45 8f 03 4f 8d 05 8e 04 45 83 07 8c 06 02 96 |.E..O....E......|
00000200 0a 0c 07 08 4a 0b 00 00 2c 00 00 00 6c 18 00 00 |....J...,...l...|
00000210 c0 b0 ff ff dd 00 00 00 00 41 0e 10 86 02 46 0d |.........A....F.|
00000220 06 47 8e 04 8f 03 45 8d 05 45 8c 06 48 83 07 02 |.G....E..E..H...|
...省略