什么是本机内存跟踪的“可维护性内存类别”?

2022-09-03 13:50:06

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

在调查过程中,我发现“可维护性内存类别”NMT不断增长(每小时15mb)。我检查了页面 https://docs.oracle.com/en/java/javase/17/troubleshoot/diagnostic-tools.html#GUID-5EF7BB07-C903-4EBD-A9C2-EC0E44048D37 但那里没有提到这个类别。

谁能解释一下这个可维护性类别意味着什么,以及什么会导致这种逐渐增加?此外,与 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更新#1:

感谢@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();
    }
}

答案 1

不幸的是(?),确定这些类别映射到什么的最简单方法是查看OpenJDK源代码。您正在寻找的 NMT 标记是 mtServiceability。这表明“可维护性”基本上是JDK / JVM中的诊断接口:JVMTI,堆转储等。

但是,从观察您显示的堆栈跟踪示例中可以清楚地看出同样的事情 - 这是转储线程信息的东西,例如 堆转储等。如果您怀疑该代码中的内存泄漏,则可以尝试构建一个MCVE来演示它,并针对OpenJDK提交错误,或者将其展示给OpenJDK开发人员。您可能更清楚应用程序正在做什么来导致线程转储,请专注于此。ThreadStackTrace::dump_stack_at_safepointjstack

话虽如此,我没有看到任何明显的内存泄漏,我也不能通过压力测试重现任何泄漏,所以也许你看到的是在越来越大的线程堆栈上“只是”线程转储。或者,在发生线程转储时捕获它。或。。。如果没有MCVE,很难说。StackFrameInfo

更新:在玩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 本周发布,因此升级后该错误应该会消失。


答案 2

某些内存波动的一个可能原因是其他一些进程使用动态附加来附加JVM并调试应用程序并将应用程序明智的信息传输到调试器。可维护性与jdb(java调试器)密切相关。

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

开放的JDK也有分析性记录

热点中的可维护性

HotSpot 虚拟机包含几种技术,这些技术允许其操作>由另一个 Java 进程观察:

可维护性代理 (SA)。可维护性代理是 HotSpot 存储库中的 Sun 私有>组件,由 HotSpot 工程师开发,用于>协助调试 HotSpot。然后他们意识到SA可以用来为最终用户制作>可维护性工具,因为它可以在正在运行的进程和核心文件中公开Java对象以及>HotSpot数据结构。

jvmstat 性能计数器。HotSpot 维护多个性能计数器>这些计数器通过 Sun 专用共享内存机制向外部进程公开。>这些计数器有时称为 perfdata。

Java Virtual Machine Tool Interface (JVM TI)。这是一个标准的 C > 接口,是 JSR 163 的参考实现 - JavaTM 平台 >Profiling Architecture JVM TI 由 HotSpot 实现,并允许>“代理”的本机代码来检查和修改 JVM 的状态。

监视和管理界面。这是一个 Sun 私有 API,允许监视和管理>热点的监视。

动态连接。这是一种 Sun 专用机制,它允许外部进程>在 HotSpot 中启动一个线程,然后可以使用该线程启动代理以在 > 即 HotSpot 中运行,并将有关 HotSpot 状态的信息发送回>external 进程。

DTrace.DTrace 是 Solaris >10 及更高版本中内置的屡获殊荣的动态跟踪工具。DTrace 探测器已添加到 HotSpot 中,当 HotSpot 在 Solaris 上运行时,允许>监视操作的许多方面。此外,>HotSpot包含一个jhelper.d文件,该文件使dtrace能够在堆栈>traces中显示Java帧。

pstack 支持。pstack 是一个 Solaris 实用程序,用于打印进程中所有>线程的堆栈迹线。HotSpot包括允许pstack显示Java>stack帧的支持。


推荐