我通过制作1024^3
(基本上是1Gb)长度的字节数组来玩Java的JVM。我在使用任务管理器(查看进程)和这个小片段之前,在数组创建之后和数组被垃圾收集器销毁之后测量了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使用最少的资源,我该如何解决这个问题?
修改
我做了一些研究并找出了要使用的术语。实际上,原生内存与堆内存的值不相似,并且通常大于堆内存。有没有办法减少使用的本机内存,使其接近堆内存?
答案 0 :(得分:10)
<强>结论:强>
使用垃圾优先(G1)GC (Java 9中的默认GC),这个垃圾收集器还缩小了堆大小(总之,它也会缩小与 ParallelOldGC (Java 7和Java 8中的默认GC)相比,garabage集合中的整体&#34;本机内存&#34;已经使用了),它很少永远不会缩小堆大小!
<强>一般强>
你的基本假设是错误的。
您假设代码段显示堆大小。这是不正确的。它显示堆利用率。这意味着&#34;我的堆使用了多少空间?&#34;。 Runtime.getRuntime().totalMemory()
显示堆大小,Runtime.getRuntime().freeMemory()
显示可用堆大小,它们的差异显示堆利用率(已用大小)
您的堆以初始大小开头,0字节利用率,因为尚未创建任何对象,以及最大堆大小。 最大堆大小描述允许垃圾收集器调整堆大小的大小(例如,如果没有足够的空间容纳非常大的对象)
作为创建空堆后的下一步,自动加载一些对象(类对象等),它们通常应该很容易适合初始堆大小。
然后,您的代码开始运行并分配对象。只要您的伊甸园空间没有更多空间(堆被分成年轻一代(伊甸园,幸存者和幸存者 - 太空)和老一代,如果您对这些细节感兴趣,请查找其他资源) ,触发垃圾收集。
在垃圾收集期间,垃圾收集器可能决定调整堆的大小(如上所述,当谈到最大堆大小时)。在您的情况下会发生这种情况,因为初始堆大小太小而不适合您的1GB对象。因此,堆大小会增加,介于初始堆大小和最大堆大小之间。
然后,在您的大对象死亡后,下一个GC 可以再次使堆变小,但不必。为什么?它低于最大堆大小,这是GC关注的所有内容。一些垃圾收集算法会再次缩小堆,有些则不会。
特别是 ParallelOldGC ,Java 7和Java 8中的默认GC,很少永远不会缩小堆。
如果您希望GC在垃圾收集过程中通过收缩来尝试保持堆大小小,请通过设置{尝试 garabage first(G1)GC {1}} Java标志。
示例:强>
这将以字节打印出所有值。
您将获得概述,两个GC如何工作以及使用其中任何一个时使用的空间。
-XX:+UseG1GC
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()
- 结果ParallelOldGC :
private static void createArray() {
byte[] arr = new byte[1024 * 1024 * 1024];
}
你可以看到,我的堆的初始大小约为260MB,允许的最大大小(GC可能决定调整大小的大小)大约为3.7 GB。
在创建阵列之前,使用了大约6MB的堆。然后创建大数组,并且我的堆大小(提交的大小)增加到1,3GB,使用大约1GB(数组)。然后我强制收集数组的垃圾收集。然而,我的堆大小保持在1,3GB,因为GC不关心再次缩小它,只是利用率下降到2MB。
- 结果G1 :
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
我们走了! G1 GC关心小堆!清理对象后,不仅利用率降低到大约0.5MB,而且堆大小也缩小到8MB(与ParallelOldGC中的1,3GB相比)
更多信息:
但是,请记住,堆大小仍将与任务管理器中显示的不同。来自following image的Wikipedia - Java virtual machine说明堆只是完整JVM内存的一部分:
答案 1 :(得分:2)
堆只是JVM内存中的一个区域。对于诸如共享库,代码,线程堆栈,直接内存和GUI组件之类的东西,JVM在最大堆大小之上额外增加200 - 400 MB并不罕见。
因此,当在该时刻可能正在使用2 MB(MB =兆字节,Mb =兆位)对象时,应用程序可以保留更多。
有没有办法减少使用的本机内存,使其接近堆内存?
您可以使用较旧版本的Java,它往往使用较少的内存,较小的最大堆和perm gen,使用较少的额外资源。
答案 2 :(得分:1)
如果对GC使用G1收集器,则可以减小堆大小,但本机内存大小不会减少。在某些应用程序中,分配的本机内存可能超过实际堆。本机堆受到Thrashing的影响。