15

我有一个在 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();
    }
}
4

2 回答 2

19

不幸的是(?),确定这些类别映射到什么的最简单方法是查看 OpenJDK 源代码。您正在寻找的 NMT 标签是mtServiceability。这将表明“可服务性”基本上是 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 本周发布,因此升级后该错误应该会消失。

于 2022-01-14T15:58:22.953 回答
1

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

https://openjdk.java.net/groups/serviceability/ 在此处输入图像描述

开放的 JDK 也对此进行了分析 记录

HotSpot 中的可维护性

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

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

jvmstat 性能计数器。HotSpot 维护着几个性能计数器,这些计数器通过 Sun 私有共享内存机制暴露给外部进程。>这些计数器有时称为 perfdata。

Java 虚拟机工具接口 (JVM TI)。这是一个标准 C >接口,它是 JSR 163 - JavaTM Platform >Profiling Architecture JVM TI 的参考实现由 HotSpot 实现,并允许本机代码 >“代理”检查和修改 JVM 的状态。

监控和管理界面。这是一个 Sun 私有 API,允许对 HotSpot 的各个方面进行监控和管理。

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

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

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

于 2022-01-14T20:39:03.603 回答