Java OutOfMemoryException,尽管使用了WeakHashMap
如果不调用Java OutOfMemoryException,尽管使用了WeakHashMap,java,java-8,garbage-collection,out-of-memory,weak-references,Java,Java 8,Garbage Collection,Out Of Memory,Weak References,如果不调用System.gc(),系统将抛出OutOfMemoryException。我不知道为什么我需要显式调用System.gc();JVM应该调用gc()本身,对吗?请告知 以下是我的测试代码: public static void main(String[] args) throws InterruptedException { WeakHashMap<String, int[]> hm = new WeakHashMap<>(); int i
System.gc()
,系统将抛出OutOfMemoryException。我不知道为什么我需要显式调用System.gc()
;JVM应该调用gc()
本身,对吗?请告知
以下是我的测试代码:
public static void main(String[] args) throws InterruptedException {
WeakHashMap<String, int[]> hm = new WeakHashMap<>();
int i = 0;
while(true) {
Thread.sleep(1000);
i++;
String key = new String(new Integer(i).toString());
System.out.println(String.format("add new element %d", i));
hm.put(key, new int[1024 * 10000]);
key = null;
//System.gc();
}
}
JVM将自己调用GC,但在这种情况下,调用太少太晚。 在这种情况下,不仅仅是GC负责清除内存。 映射值是强可访问的,当对其调用某些操作时,映射本身会清除这些值 以下是打开GC事件(XX:+PrintGC)时的输出: 直到最后一次尝试将值放入映射时,才会触发GC 在引用队列上出现映射键之前,WeakHashMap无法清除过时的条目。 映射键在被垃圾收集之前不会出现在引用队列上。 新映射值的内存分配在映射有机会清除自身之前触发。 当内存分配失败并触发GC时,会收集映射键。但这太少太晚了-没有足够的内存被释放来分配新的映射值。 如果您减少有效负载,您可能最终拥有足够的内存来分配新的映射值,过时的条目将被删除 另一个解决方案是将值本身包装到WeakReference中。这将允许GC清除资源,而无需等待map自行完成。 以下是输出:
add new element 1
add new element 2
add new element 3
add new element 4
add new element 5
add new element 6
add new element 7
[GC (Allocation Failure) 2407753K->2400920K(2801664K), 0.0133492 secs]
[GC (Allocation Failure) 2400920K->2400888K(2801664K), 0.0090964 secs]
[Full GC (Allocation Failure) 2400888K->806K(190976K), 0.1053405 secs]
add new element 8
add new element 9
add new element 10
add new element 11
add new element 12
add new element 13
[GC (Allocation Failure) 2402096K->2400902K(2801664K), 0.0108237 secs]
[GC (Allocation Failure) 2400902K->2400838K(2865664K), 0.0058837 secs]
[Full GC (Allocation Failure) 2400838K->1024K(255488K), 0.0863236 secs]
add new element 14
add new element 15
...
(and counting)
好多了。另一个答案确实正确,我编辑了我的答案。作为一个小附录,
G1GC
不会表现出这种行为,不像ParallelGC
;这是java-8
下的默认值
如果我将您的程序稍微更改为(使用-Xmx20m在jdk-8
下运行),您认为会发生什么情况
我使用jdk-13运行的(其中,G1GC
是默认值)
以下是部分日志:
[2.082s][debug][gc,ergo] Request concurrent cycle initiation (requested by GC cause). GC cause: G1 Humongous Allocation
这已经做了一些不同的事情。它启动了一个并发循环
(在应用程序运行时完成),因为有一个G1巨大的分配
。作为这个并发周期的一部分,它执行一个年轻的GC周期(在运行时停止应用程序)
作为年轻GC的一部分,它还清除了巨大的区域
您现在可以看到,jdk-13
在分配真正大的对象时,不会等待垃圾在旧区域堆积,而是触发了一个并发的GC循环,从而节省了时间;与jdk-8不同
您可能希望阅读DisableExplicitGC
和/或ExplicitGCInvokesConcurrent
的意思,再加上System.gc
,并理解调用System.gc
实际上有帮助的原因 什么jdk版本?您是否使用任何-Xms和-Xmx参数?你在哪一步得到OOM?我无法在我的系统上复制。在调试模式下,我可以看到GC正在完成它的工作。你能在调试模式下检查地图是否真的被清除了吗?jre 1.8.0_212-b10-Xmx200m你可以从我附加的gc日志中看到更多细节;对于你的回答,你的结论似乎是正确的;当我尝试将有效负载从1024*10000减少到1024*1000时;代码可以很好地工作;但我还是不太明白你的解释;正如你的意思,如果需要从WeakHashMap中释放空间,应该至少做两次gc;第一次是从map中收集密钥,并将其添加到引用队列中;第二次是收集值?但是从您提供的第一个日志来看,实际上JVM已经使用了两次完整的gc;您的意思是,“映射值是强可访问的,并且在对其调用某些操作时被映射本身清除。”。从哪里可以访问它们?在您的案例中,仅仅运行两次GC是不够的。首先,您需要运行一次GC,这是正确的。但下一步将需要与地图本身进行一些交互。您应该寻找的是方法java.util.WeakHashMap.expungeStaleEntries
,它读取引用队列并从映射中删除条目,从而使值无法访问并受收集约束。只有在完成后,第二次GC将释放一些内存expungestalentries
在许多情况下都会被调用,比如get/put/size或者几乎所有你通常用地图做的事情。“这就是问题所在。”安德罗尼卡,这是WeakHashMap中迄今为止最令人困惑的部分。它被多次报道@安德洛尼卡,尤其是下半场,可能也会有所帮助。另外,Java8默认情况下不使用G1GC。OP的GC日志也清楚地表明,它正在为旧一代使用并行GC。对于这样一个非并发收集器,它就像@Holger中描述的一样简单。今天早上我在查看这个答案时,才意识到它确实是ParalleGC
,我已经编辑过了,很抱歉(并感谢)证明我错了。“巨大的分配”仍然是一个正确的提示。对于非并发收集器,这意味着第一个GC将在旧一代已满时运行,因此无法回收足够的空间将使其致命。相反,当您减小阵列大小时,当旧一代中仍有内存时,将触发年轻的GC,因此收集器可以升级对象并继续。另一方面,对于并发收集器,在堆耗尽之前触发gc是正常的,因此-XX:+UseG1GC
使其在Java8中工作,就像-XX:+UseParallelOldGC
使其在新JVM中失败一样。
add new element 1
add new element 2
add new element 3
add new element 4
add new element 5
add new element 6
add new element 7
[GC (Allocation Failure) 2407753K->2400920K(2801664K), 0.0133492 secs]
[GC (Allocation Failure) 2400920K->2400888K(2801664K), 0.0090964 secs]
[Full GC (Allocation Failure) 2400888K->806K(190976K), 0.1053405 secs]
add new element 8
add new element 9
add new element 10
add new element 11
add new element 12
add new element 13
[GC (Allocation Failure) 2402096K->2400902K(2801664K), 0.0108237 secs]
[GC (Allocation Failure) 2400902K->2400838K(2865664K), 0.0058837 secs]
[Full GC (Allocation Failure) 2400838K->1024K(255488K), 0.0863236 secs]
add new element 14
add new element 15
...
(and counting)
public static void main(String[] args) throws InterruptedException {
WeakHashMap<String, int[]> hm = new WeakHashMap<>();
int i = 0;
while (true) {
Thread.sleep(200);
i++;
String key = "" + i;
System.out.println(String.format("add new element %d", i));
hm.put(key, new int[512 * 1024 * 1]); // <--- allocate 1/2 MB
}
}
public static void main(String[] args) throws InterruptedException {
Map<String, int[]> hm = new WeakHashMap<>();
int i = 0;
while (true) {
Thread.sleep(1000);
i++;
String key = "" + i;
System.out.println(String.format("add new element %d", i));
hm.put(key, new int[1024 * 1024 * 1]); // <--- 1 MB allocation
}
}
java -Xmx20m "-Xlog:gc*=debug" gc.WeakHashMapTest
[2.082s][debug][gc,ergo] Request concurrent cycle initiation (requested by GC cause). GC cause: G1 Humongous Allocation
[2.082s][info ][gc,start] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation)