Native Memory Tracking中的“serviceability memory category”是什么?

16

我有一个运行在 docker 容器中的 Java 应用程序(JDK13)。最近,我将该应用程序迁移到 JDK17 (OpenJDK17),并发现 docker 容器的内存使用逐渐增加。

在调查过程中,我发现 'serviceability memory category' NMT 不断增长(每小时增加15MB)。我查看了页面 https://docs.oracle.com/en/java/javase/17/troubleshoot/diagnostic-tools.html#GUID-5EF7BB07-C903-4EBD-A9C2-EC0E44048D37,但该类别未在其中提及。

有人能解释一下这个 serviceability 类别的含义以及可能导致这种逐渐增加的原因吗?另外,在 JDK13 中,还存在一些新的附加内存类别。也许有人知道我可以阅读关于它们的详细信息的地方。

以下是命令 jcmd 1 VM.native_memory summary 的结果:

Native Memory Tracking:

(Omitting categories weighting less than 1KB)

Total: reserved=4431401KB, committed=1191617KB
-                 Java Heap (reserved=2097152KB, committed=479232KB)
                            (mmap: reserved=2097152KB, committed=479232KB) 
 
-                     Class (reserved=1052227KB, committed=22403KB)
                            (classes #29547)
                            (  instance classes #27790, array classes #1757)
                            (malloc=3651KB #79345) 
                            (mmap: reserved=1048576KB, committed=18752KB) 
                            (  Metadata:   )
                            (    reserved=139264KB, committed=130816KB)
                            (    used=130309KB)
                            (    waste=507KB =0.39%)
                            (  Class space:)
                            (    reserved=1048576KB, committed=18752KB)
                            (    used=18149KB)
                            (    waste=603KB =3.21%)
 
-                    Thread (reserved=387638KB, committed=40694KB)
                            (thread #378)
                            (stack: reserved=386548KB, committed=39604KB)
                            (malloc=650KB #2271) 
                            (arena=440KB #752)
 
-                      Code (reserved=253202KB, committed=76734KB)
                            (malloc=5518KB #23715) 
                            (mmap: reserved=247684KB, committed=71216KB) 
 
-                        GC (reserved=152419KB, committed=92391KB)
                            (malloc=40783KB #34817) 
                            (mmap: reserved=111636KB, committed=51608KB) 
 
-                  Compiler (reserved=1506KB, committed=1506KB)
                            (malloc=1342KB #2557) 
                            (arena=165KB #5)
 
-                  Internal (reserved=5579KB, committed=5579KB)
                            (malloc=5543KB #33822) 
                            (mmap: reserved=36KB, committed=36KB) 
 
-                     Other (reserved=231161KB, committed=231161KB)
                            (malloc=231161KB #347) 
 
-                    Symbol (reserved=30558KB, committed=30558KB)
                            (malloc=28887KB #769230) 
                            (arena=1670KB #1)
 
-    Native Memory Tracking (reserved=16412KB, committed=16412KB)
                            (malloc=575KB #8281) 
                            (tracking overhead=15837KB)
 
-        Shared class space (reserved=12288KB, committed=12136KB)
                            (mmap: reserved=12288KB, committed=12136KB) 
 
-               Arena Chunk (reserved=18743KB, committed=18743KB)
                            (malloc=18743KB) 
 
-                   Tracing (reserved=32KB, committed=32KB)
                            (arena=32KB #1)
 
-                   Logging (reserved=7KB, committed=7KB)
                            (malloc=7KB #289) 
 
-                 Arguments (reserved=1KB, committed=1KB)
                            (malloc=1KB #53) 
 
-                    Module (reserved=1045KB, committed=1045KB)
                            (malloc=1045KB #5026) 
 
-                 Safepoint (reserved=8KB, committed=8KB)
                            (mmap: reserved=8KB, committed=8KB) 
 
-           Synchronization (reserved=204KB, committed=204KB)
                            (malloc=204KB #2026) 
 
-            Serviceability (reserved=31187KB, committed=31187KB)
                            (malloc=31187KB #49714) 
 
-                 Metaspace (reserved=140032KB, committed=131584KB)
                            (malloc=768KB #622) 
                            (mmap: reserved=139264KB, committed=130816KB) 
 
-      String Deduplication (reserved=1KB, committed=1KB)
                            (malloc=1KB #8) 

增加内存的详细信息如下:

[0x00007f6ccb970cbe] OopStorage::try_add_block()+0x2e
[0x00007f6ccb97132d] OopStorage::allocate()+0x3d
[0x00007f6ccbb34ee8] StackFrameInfo::StackFrameInfo(javaVFrame*, bool)+0x68
[0x00007f6ccbb35a64] ThreadStackTrace::dump_stack_at_safepoint(int)+0xe4
                             (malloc=6755KB type=Serviceability #10944)

2022-01-17的更新:

感谢@Aleksey Shipilev的帮助!我们已经找到了一个导致问题的地方,与许多ThreadMXBean#.dumpAllThreads调用有关。这是MCVE,Test.java:

运行命令:

java -Xmx512M -XX:NativeMemoryTracking=detail Test.java 

并定期检查结果中的服务等级。

jcmd YOUR_PID VM.native_memory summary 

测试 Java:

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Test {

    private static final int RUNNING = 40;
    private static final int WAITING = 460;

    private final Object monitor = new Object();
    private final ThreadMXBean threadMxBean = ManagementFactory.getThreadMXBean();
    private final ExecutorService executorService = Executors.newFixedThreadPool(RUNNING + WAITING);

    void startRunningThread() {
        executorService.submit(() -> {
            while (true) {
            }
        });
    }

    void startWaitingThread() {
        executorService.submit(() -> {
            try {
                monitor.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

    void startThreads() {
        for (int i = 0; i < RUNNING; i++) {
            startRunningThread();
        }

        for (int i = 0; i < WAITING; i++) {
            startWaitingThread();
        }
    }

    void shutdown() {
        executorService.shutdown();
        try {
            executorService.awaitTermination(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    
    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();

        Runtime.getRuntime().addShutdownHook(new Thread(test::shutdown));

        test.startThreads();

        for (int i = 0; i < 12000; i++) {
            ThreadInfo[] threadInfos = test.threadMxBean.dumpAllThreads(false, false);
            System.out.println("ThreadInfos: " + threadInfos.length);

            Thread.sleep(100);
        }

        test.shutdown();
    }
}
2个回答

19

不幸的是,确定这些类别映射到哪里最简单的方法是查看OpenJDK源代码。您要查找的NMT标记是mtServiceability。这将显示“serviceability”基本上是JDK/JVM中的诊断接口:JVMTI、堆转储等。

但是,从观察您正在展示的堆栈跟踪样本也可以清楚地看出同样类型的东西提及了ThreadStackTrace::dump_stack_at_safepoint——这是一些转储线程信息的内容,例如用于jstack、堆转储等。如果您怀疑该代码中存在内存泄漏,可以尝试构建一个MCVE来演示它,并将错误提交给OpenJDK,或向OpenJDK开发人员展示它。您可能更了解导致线程转储的应用程序在做什么方面需要重点关注。

话虽如此,我没有在StackFrameInfo中看到任何明显的内存泄漏,也无法通过压力测试重现任何泄漏。所以您看到的可能只是线程越来越大的线程堆栈转储。或者您在进行线程转储时捕获它。或者...... 没有MCVE很难说。

更新:在尝试了MCVE后,我意识到它能够在17.0.1中复现,但是在主线开发JDK、JDK 18 EA或JDK 17.0.2 EA中都无法复现。之前我已经测试过17.0.2 EA,所以没有看到这个问题,真是糟糕。在17.0.1和17.0.2 EA之间进行二分显示,这个问题已经通过JDK-8273902的回溯修复了。本周将发布17.0.2版本,因此升级后应该可以解决这个bug。

@Alexey Shipilev 谢谢您的帮助!您是对的,NMT内存泄漏的可维护性类别是由于定期转储线程信息引起的。我在问题描述中添加了一个MCVE(“2022-01-17更新#1”部分)。您认为它可以被视为JDK17的错误吗?它可以在OpenJDK 17.0.1和OracleJDK 17.0.1上重现,但在OpenJDK 13.0.1上运行良好。 - Egor
2
很好,提供最小可复现示例将有助于进一步诊断,让我来看看。 - Aleksey Shipilev
2
好的,我修改了答案:这是一个bug,并且有一个修复程序已经被移植到17.0.2版本。 - Aleksey Shipilev
太好了,它将在17.0.2中得到修复!非常感谢@Alexey Shipilev的帮助! - Egor

1
一些内存波动的可能原因是,有其他进程使用动态附加到JVM并调试应用程序,并将应用程序信息传输给调试器。 "可服务性" 与 jdb(Java调试器)密切相关。

https://openjdk.java.net/groups/serviceability/ enter image description here

开放JDK也有这方面的分析文档记录
HotSpot中的可服务性
HotSpot虚拟机包含多种技术,允许另一个Java进程观察其操作:
- Serviceability Agent(SA)。Serviceability Agent是HotSpot存储库中的Sun私有组件,由HotSpot工程师开发,以协助调试HotSpot。然后他们意识到SA可以用于为终端用户制作可服务性工具,因为它可以在运行过程中和核心文件中暴露Java对象以及HotSpot数据结构。 - jvmstat性能计数器。HotSpot通过Sun私有共享内存机制维护几个性能计数器,这些计数器对外部进程暴露。有时这些计数器被称为perfdata。 - Java虚拟机工具接口(JVM TI)。这是一个标准C接口,是JSR 163 - JavaTM平台剖析架构的参考实现。JVM TI由HotSpot实现,允许本地代码“代理”检查和修改JVM的状态。 - 监控和管理接口。这是Sun的私有API,允许监视和管理HotSpot的某些方面。 - 动态连接。这是一种Sun私有机制,允许外部进程启动HotSpot中的线程,然后使用该线程来启动代理,在HotSpot中运行,并向外部进程发送有关HotSpot状态的信息。 - DTrace。DTrace是内置于Solaris 10及更高版本中的获奖动态跟踪工具。已向HotSpot添加了DTrace探针,允许在Solaris上运行HotSpot时监视许多操作方面。此外,HotSpot包含一个jhelper.d文件,使dtrace可以显示堆栈跟踪中的Java帧。 - pstack支持。pstack是一种Solaris实用程序,可打印进程中所有线程的堆栈跟踪。HotSpot包括支持,允许pstack显示Java堆栈帧。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接