结论:
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 内存的一部分: