NMT即Native Memory Tracker
使用jcmd参数配合JVM配置可以开启NMT选项:
-XX:NativeMemoryTracking=summary/detail
jcmd <pid> VM.native_memory summary/detail
输出的结果代表内存使用情况
NMT结果中的各项属性分析
Java Heap
Java Heap (reserved=9437184KB, committed=9437184KB)
(mmap: reserved=9437184KB, committed=9437184KB)
summary部分可以看到,预留和实际使用
[0x0000000580000000 - 0x00000007c0000000] reserved 9437184KB for Java Heap from
[0x00007f424c869f4f] ReservedHeapSpace::ReservedHeapSpace(unsigned long, unsigned long, bool, char*)+0xbf
[0x00007f424c82c080] Universe::reserve_heap(unsigned long, unsigned long)+0x1f0
[0x00007f424c2495ec] G1CollectedHeap::initialize()+0x18c
[0x00007f424c82c846] Universe::initialize_heap()+0x176
[0x0000000580000000 - 0x00000007c0000000] committed 9437184KB from
……
地址分配部分可以看到0x0000000580000000 - 0x00000007c0000000是地址区间,后面有预留值。下面可以知道用的是G1回收期,说明了调用堆栈以及方法地址。
reserved 代表了计划申请的内存,committed 代表了实际申请的物理内存,加之 JVM 具有动态回收的特性,它回收的内存可能不会立马还给操作系统,这里会有一点出入
Class
java8以后就是占用的metaspace元数据空间。
Class (reserved=1196248KB, committed=168408KB)
(classes #29174)
(malloc=4312KB #64698)
(mmap: reserved=1191936KB, committed=164096KB)
可以看到预留占了1G多,可能不太符合-XX:MaxMetaspaceSize
参数配置,这是由于指针压缩导致的。
-XX:UseCompressedOops
使用这个参数,默认是不开启的,当开启压缩后,可以查看reserve具体日志:
[0x00000007c0000000 - 0x0000000800000000] reserved 1048576KB for Class from
[0x00007f424c869473] ReservedSpace::ReservedSpace(unsigned long, unsigned long, bool, char*, unsigned long)+0x93
[0x00007f424c602a9b] Metaspace::allocate_metaspace_compressed_klass_ptrs(char*, unsigned char*)+0x4b
[0x00007f424c603723] Metaspace::global_initialize()+0x733
[0x00007f424c82cb55] universe_init()+0x85
1G都是这个堆栈申请的,allocate_metaspace_compressed_klass_ptrs
方法,这里默认申请的就是1G,可以使用如下参数进行限制:
-XX:CompressedClassSpaceSize=256M
Thread
Thread (reserved=562844KB, committed=562844KB)
(thread #547)
(stack: reserved=560328KB, committed=560328KB)
(malloc=1881KB #3276)
(arena=635KB #1087)
thread部分547意味着线程数量,实际使用了562M内存
[0x00007f4248d97000 - 0x00007f4248e97000] reserved and committed 1024KB for Thread Stack from
[0x00007f424c1387b0] ConcurrentGCThread::initialize_in_thread()+0x20
[0x00007f424c16960b] ConcurrentMarkThread::run()+0x2b
[0x00007f424c688f70] java_start(Thread*)+0x170
reserve部分日志看一看看到线程默认会预留1M,是x86平台的默认配置
也可以使用如下参数限制:
-XX:ThreadStackSize
Code
存储native code使用,使用如下参数可以进行限制
-XX:InitialCodeCacheSize=128M
GC
[0x00007f41bce00000 - 0x00007f41c5e00000] reserved 147456KB for GC from
[0x00007f424c869237] ReservedSpace::ReservedSpace(unsigned long, unsigned long)+0x117
[0x00007f424c24997c] G1CollectedHeap::initialize()+0x51c
[0x00007f424c82c846] Universe::initialize_heap()+0x176
[0x00007f424c82cb12] universe_init()+0x42
使用G1的可以看到申请了很多ReservedSpace即Rset,是G1空间换时间的表现。所以内存增大,G1占内存也会对应增大,8G大约有400M+
Compiler
编译时code内存
internal
Internal 包含命令行解析器使用的内存、JVMTI、PerfData 以及 Unsafe 分配的内存等等
JVMTI:是开发和监视 JVM 所使用的编程接口
PerfData:是 JVM 中用来记录一些指标数据的文件
Unsafe:需要关注,Nio,Netty需要关注,Unsafe_AllocateMemory申请内存格外需要注意
-XX:MaxDirectMemorySize=256M
应使用上面的JVM参数限制
Symbol
Symbol 为 JVM 中的符号表所使用的内存,主要是常量池占用内存
Arena Chunk
Arena 是 JVM 分配的一些 Chunk(内存块),当退出作用域或离开代码区域时,内存将从这些 Chunk 中释放出来。然后这些 Chunk 就可以在其他子系统中重用
Unknown
Unknown 则是下面几种情况:
当内存类别无法确定时;
当 Arena 用作堆栈或值对象时;
当类型信息尚未到达时。
结合BIO实现NMT自动输出
public void nativeMemoryPrint() {
String host = "";
try {
host = InetAddress.getLocalHost().getHostAddress();
String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
String rollup = Files.readAllLines(Paths.get("/proc/" + pid + "/smaps_rollup"))
.stream()
.collect(Collectors.joining("\n"));
log.info("{} 容器实例内存监控 pod : {}", host, rollup);
Process exec = Runtime.getRuntime().exec(new String[]{"jcmd", pid, "VM.native_memory"});
String nativeMemoryLog = new BufferedReader(new InputStreamReader(exec.getInputStream())).lines().collect(Collectors.joining("\n"));
log.info("{} 容器实例内存监控 JVM 进程 : {}", host, nativeMemoryLog);
} catch (IOException e) {
log.error("{} 容器实例内存监控出错 : {}", host, e.getLocalizedMessage());
e.printStackTrace();
}
}
这里除了使用了NMT,还是要了smaps_rollup命令,可以参考下输出结果:
> cat /proc/23/smaps_rollup
689000000-fffff53a9000 ---p 00000000 00:00 0 [rollup]
Rss: 5870852 kB
Pss: 5849120 kB
Pss_Anon: 5842756 kB
Pss 的值可以理解为进程实际占用内存大小,当Pss值即将超过宿主机内存,容器就有被k8s杀掉的风险
评论区