【问题标题】:Java's RAM usage doesn't correspond to what the Task Manager saysJava 的 RAM 使用情况与任务管理器所说的不符
【发布时间】:2016-02-21 17:18:26
【问题描述】:

我一直在通过制作1024^3(基本上是 1Gb)长度的字节数组来玩 Java 的 JVM。我使用任务管理器(查看进程)和这个小 sn-p:

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并保持在那里,即使sn-p显示为2Mb。

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

编辑:

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

【问题讨论】:

  • 你可以使用garabage first (G1) GC,它不仅会在需要时增加堆大小,如果可能的话还会再次缩小它。看看我的答案,看看如何使用它。

标签: java memory jvm ram


【解决方案1】:

结论:

使用 garbage first (G1) GC(Java 9 中的默认 GC),此垃圾收集器还会缩小 堆大小(总而言之,这也会缩小与 ParallelOldGC(Java 7 和 Java 8 中的默认 GC)相比,在垃圾收集上使用的整体“本机内存”,很少从不缩小 堆大小


一般情况:

你的基本假设是错误的。

您假设您的代码 sn-p 显示 堆大小。这是不正确的。它显示了堆利用率。这意味着“我的堆使用了多少空间?”。 Runtime.getRuntime().totalMemory() 显示堆大小Runtime.getRuntime().freeMemory() 显示空闲堆大小,它们的区别显示堆利用率(已用大小)

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

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

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

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

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

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

如果您希望 GC 也尝试通过在垃圾回收期间缩小堆大小来保持 堆大小,请尝试通过设置 @ 来尝试 garabage first (G1) GC 987654327@Java 标志。

示例:

这将打印出字节中的所有值。

您将大致了解两种 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];
}

--Result 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 相比)

更多信息:

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

【讨论】:

  • 有人知道是否有计划让 G1 GC 在不进行完整 GC 的情况下同时缩小堆?
  • @PiotrKołaczkowski:我不知道,但一般的问题是:为什么要这样做?
  • 因为使用 G1 GC 的主要目标之一是避免大停顿。我不想暂停我的应用程序几秒钟,只是将所有这些空闲区域释放到操作系统。
  • @PiotrKołaczkowski:我认为向操作系统释放空区域只占用很少的时间。这些区域已经是空的,它们只需要被释放。然而,我不得不承认,我目前没有任何指标支持我的假设。可能将此作为一个新问题打开,这里有一些真正的 JVM 专家。
【解决方案2】:

堆只是 JVM 内存中的一个区域。对于 JVM 来说,除了共享库、代码、线程堆栈、直接内存和 GUI 组件之类的最大堆大小之外,还有 200 - 400 MB 的额外空间并不罕见。

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

有没有办法减少使用的本机内存,使其接近堆内存?

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

【讨论】:

  • 一票否决?
  • 在我看来,使用较旧的 Java 版本并不是一个好的解决方案,为什么不直接切换底层的垃圾收集器呢?我为此写了一个答案,有了显着的改进。
  • @MarkusWeninger 缩小堆大小会有所帮助,但通常最受关注的是非堆内存使用情况。
  • 是的,但在 OPs 的情况下,我们可以看到问题是由于非收缩堆造成的,因为在他的 1GB 对象死亡后堆大小保持不变。因为否则任务管理器中显示的 RAM 利用率应该变回大对象分配之前的某个位置。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 2014-05-27
    • 2011-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-21
    • 2014-08-26
    相关资源
    最近更新 更多