Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/jpa/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java OutOfMemoryException,尽管使用了WeakHashMap_Java_Java 8_Garbage Collection_Out Of Memory_Weak References - Fatal编程技术网

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)