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

Java's RAM usage doesn't correspond to what the Task Manager says

我一直在玩 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使用最少的资源,我该如何解决这个问题?

编辑:

我已经做了一些研究并弄清楚了要使用的术语。事实上,native 内存的值并不像 heap 内存的值,并且通常大于堆内存。有没有办法减少使用的本机内存,使其接近堆内存?

堆只是JVM 内存中的一个区域。对于共享库、代码、线程堆栈、直接内存和 GUI 组件等事物,JVM 在最大堆大小之上额外增加 200 - 400 MB 并不罕见。

因此,当 2 MB(MB = 兆字节,Mb = 兆位)的对象当时可能正在使用时,应用程序可以保留更多。

Is there a way to reduce the native memory used, so that it is close to the heap memory?

您可以使用旧版本的 Java,它倾向于使用更少的内存、更小的最大堆和 perm gen,使用更少的额外资源。

结论:

使用垃圾优先 (G1) GC(Java 9 中的默认 GC),此垃圾收集器还缩小了 堆大小(总而言之,与 ParallelOldGC(Java 7 中的默认 GC 和Java 8),这很少甚至从不缩小 堆大小!


一般:

你的基本假设是错误的。

您假设您的代码片段显示堆大小。这是不正确的。它显示 堆利用率 。这意味着 "How much space of my heap is used?"。 Runtime.getRuntime().totalMemory() 显示 堆大小 Runtime.getRuntime().freeMemory() 显示 空闲堆大小 ,它们的差异显示 堆利用率(已用大小).

您的堆以 初始大小 开始,具有 0 字节 利用率 因为尚未创建对象,并且 最大堆大小最大堆大小 描述允许垃圾收集器调整堆大小的大小(例如,如果没有足够的 space 用于非常大的对象)

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

然后,您的代码开始 运行 并分配对象。一旦你的 Eden space 中不再有 space(堆被分成年轻一代(Eden、survivor-from 和 survivor-to space)和年老一代,如果您对这些详细信息感兴趣,请查找其他资源),将触发垃圾回收。

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

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

尤其是 ParallelOldGC,Java 7 和 Java 8 中的默认 GC,很少甚至从不收缩堆。

如果你想要一个 GC 也试图通过在垃圾收集期间缩小它来保持 堆大小 小,请尝试 garabage 优先 (G1) GC 通过设置 -XX:+UseG1GC Java 标志。

示例:

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

您将了解两个 GC 的工作原理以及使用其中一个时使用了多少 space。

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 的堆。然后创建大数组,我的 heap size(承诺大小)增加到 1.3GB,使用了大约 1GB(数组)。然后我强制进行垃圾收集,收集数组。然而,我的 heap size 保持在 1.3GB,因为 GC 不关心再次缩小它,只是 utilization 下降到 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 关心小堆!清理对象后,不仅 utilization 下降到大约 0.5MB,而且 heap size 也缩小到 8MB(与ParallelOldGC 中 1,3GB)

更多信息:

但是,请记住,堆大小 仍然与任务管理器中显示的不同。 following image from Wikipedia - Java virtual machine说明堆只是整个JVM内存的一部分:

如果您使用 G1 收集器进行 GC,您可以减少堆大小,但本机内存大小不会减少。在某些应用程序中,分配的本机内存可能比实际堆多。本机堆遭受抖动。