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