Java 的 RAM 使用情况与任务管理器显示的不符

2024-01-03

我一直在玩Java的JVM,制作一个1024^3(基本上 1Gb)长度字节数组。我使用任务管理器(查看进程)和这个小片段测量了数组创建之前、之后以及数组被垃圾收集器销毁之后的 RAM 使用情况:

public static void showMemory() {
    System.out.println("Memory used: "
            + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024.D * 1024.D) + "mB.");
}

上述代码分别显示 2Mb、1029Mb 和 2Mb。 -> 这一切看起来都很正常。 然而,当查看 TaskManager 时,Java 的 RAM 使用量最初为 2MB,然后达到 1052Mb 并保持在那里,即使代码片段显示 2Mb。

我想让Java使用最少的资源,我该如何解决这个问题?

Edit:

我做了一些研究并找出了要使用的术语。事实上,native记忆并不像heap内存的价值,并且往往大于堆内存。有没有办法减少本机内存的使用,使其接近堆内存?


结论:

Use the 垃圾优先(G1)GC(Java 9 中的默认 GC),此垃圾收集器还会缩小堆大小(总而言之,这也会缩小垃圾集合上使用的总体“本机内存”),与并行OldGC(Java 7 和 Java 8 中的默认 GC),很少甚至从不收缩堆大小!


一般来说:

你的基本假设是错误的。

您假设您的代码片段显示堆大小。这是不正确的。它显示了堆利用率。这意味着“我的堆使用了多少空间?”。Runtime.getRuntime().totalMemory()显示了堆大小, Runtime.getRuntime().freeMemory()显示了可用堆大小,他们的差异表明堆利用率(已用大小).

你的堆开始于初始尺寸, 0 字节利用率因为还没有创建任何对象,并且最大堆大小. 最大堆大小描述允许垃圾收集器调整堆大小的大小(例如,如果没有足够的空间容纳非常大的对象)

作为创建空堆后的下一步,会自动加载一些对象(类对象等),它们通常应该很容易适合初始堆大小。

然后,您的代码开始运行并分配对象。一旦 Eden 空间中没有更多空间(堆被分为年轻代(Eden、幸存者来自和幸存者到空间)和老年代,如果您对这些详细信息感兴趣,请查找其他资源) ,触发垃圾收集。

在垃圾收集期间,垃圾收集器可能会决定调整堆的大小(如上所述,在讨论时)最大堆大小)。这发生在你的情况下,因为你的初始堆大小太小,无法容纳您的 1GB 对象。因此堆大小增加,介于之间初始堆大小 and 最大堆大小.

然后,在你的大对象死亡后,下一次GCcould再次使堆变小,但它不必。为什么?它位于以下最大堆大小,这就是 GC 所关心的。有些垃圾收集算法会再次缩小堆,有些则不会。

尤其是并行OldGC,Java 7 和 Java 8 中的默认 GC,很少从不收缩堆。

如果你想要一个也试图保留的 GC堆大小通过在垃圾收集期间缩小它来变小,尝试垃圾第一(G1)GC通过设置-XX:+UseG1GC爪哇标志。

Example:

这将以字节为单位打印出所有值。

您将了解两个 GC 的工作原理以及使用它们时使用了多少空间。

System.out.println(String.format("Init:\t%,d",ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getInit()));
System.out.println(String.format("Max:\t%,d%n", ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax()));

Thread outputThread = new Thread(() -> {
    try {
        int i = 0;
        for(;;) {
            System.out.println(String.format("%dms\t->\tUsed:\t\t%,d", i, ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed()));
            System.out.println(String.format("%dms\t->\tCommited:\t%,d", i, ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getCommitted()));
            Thread.sleep(100);
            i += 100;
        }
    } catch (Exception e) { }
});

Thread allocThread = new Thread(() -> {
    try {
        int val = 0;
        Thread.sleep(500); // Wait 1/2 second
        createArray();
        Thread.sleep(500); // Wait another 1/2 seconds
        System.gc(); // Force a GC, array should be cleaned
        return;
    } catch (Exception e) { }
});

outputThread.start();
allocThread.start();

createArray()无非是下面这个小方法:

private static void createArray() {
    byte[] arr = new byte[1024 * 1024 * 1024];
}

--结果ParallelOldGC:

Init:   262,144,000
Max:    3,715,629,056

0ms ->  Used:       6,606,272
0ms ->  Commited:   251,658,240
100ms   ->  Used:       6,606,272
100ms   ->  Commited:   251,658,240
200ms   ->  Used:       6,606,272
200ms   ->  Commited:   251,658,240
300ms   ->  Used:       6,606,272
300ms   ->  Commited:   251,658,240
400ms   ->  Used:       6,606,272
400ms   ->  Commited:   251,658,240
500ms   ->  Used:       1,080,348,112
500ms   ->  Commited:   1,325,924,352
600ms   ->  Used:       1,080,348,112
600ms   ->  Commited:   1,325,924,352
700ms   ->  Used:       1,080,348,112
700ms   ->  Commited:   1,325,924,352
800ms   ->  Used:       1,080,348,112
800ms   ->  Commited:   1,325,924,352
900ms   ->  Used:       1,080,348,112
900ms   ->  Commited:   1,325,924,352
1000ms  ->  Used:       1,080,348,112
1000ms  ->  Commited:   1,325,924,352
1100ms  ->  Used:       1,080,348,112
1100ms  ->  Commited:   1,325,924,352
1200ms  ->  Used:       2,261,768
1200ms  ->  Commited:   1,325,924,352
1300ms  ->  Used:       2,261,768
1300ms  ->  Commited:   1,325,924,352

您可以看到,我的堆开始时的初始大小约为 260MB,允许的最大大小(GC 可能决定调整堆大小的大小)约为 3.7 GB。

在创建数组之前,我使用了大约 6MB 的堆。然后创建大数组,然后我的堆大小(承诺大小)增加到 1.3GB,使用了大约 1GB(阵列)。然后我强制进行垃圾回收,以收集数组。然而,我的堆大小保持在 1.3GB,因为 GC 不关心再次缩小它,只关心利用率减少 2MB。

--结果G1:

Init:   262,144,000
Max:    4,179,623,936

0ms ->  Used:       2,097,152
0ms ->  Commited:   262,144,000
100ms   ->  Used:       2,097,152
100ms   ->  Commited:   262,144,000
200ms   ->  Used:       2,097,152
200ms   ->  Commited:   262,144,000
300ms   ->  Used:       2,097,152
300ms   ->  Commited:   262,144,000
400ms   ->  Used:       2,097,152
400ms   ->  Commited:   262,144,000
500ms   ->  Used:       1,074,364,464
500ms   ->  Commited:   1,336,934,400
600ms   ->  Used:       1,074,364,464
600ms   ->  Commited:   1,336,934,400
700ms   ->  Used:       1,074,364,464
700ms   ->  Commited:   1,336,934,400
800ms   ->  Used:       1,074,364,464
800ms   ->  Commited:   1,336,934,400
900ms   ->  Used:       1,074,364,464
900ms   ->  Commited:   1,336,934,400
1000ms  ->  Used:       492,520
1000ms  ->  Commited:   8,388,608
1100ms  ->  Used:       492,520
1100ms  ->  Commited:   8,388,608
1200ms  ->  Used:       492,520
1200ms  ->  Commited:   8,388,608

现在我们开始! G1 GC 关心小堆!物体清洁后,不仅利用率下降到约 0.5MB,但堆大小缩小至 8MB(与 ParallelOldGC 中的 1.3GB 相比)

更多信息:

但是,请记住,堆大小仍然与任务管理器中显示的有所不同。这下图 https://commons.wikimedia.org/wiki/File:JvmSpec7.png from 维基百科 - Java 虚拟机 https://en.wikipedia.org/wiki/Java_virtual_machine说明堆只是完整 JVM 内存的一部分:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 的 RAM 使用情况与任务管理器显示的不符 的相关文章

随机推荐