目 录CONTENT

文章目录

NMT - 内存追踪区域分析

FatFish1
2024-10-31 / 0 评论 / 0 点赞 / 45 阅读 / 0 字 / 正在检测是否收录...

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杀掉的风险

参考资料

参考资料:1. https://juejin.cn/post/7360942283556864040

0

评论区