OutOfMemoryException 尽管使用 WeakHashMap
OutOfMemoryException despite using WeakHashMap
如果不调用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();
}
}
如下,添加-XX:+PrintGCDetails
打印GC信息;如您所见,实际上,JVM 尝试执行完整的 GC 运行,但失败了;我还是不知道原因。很奇怪,如果我取消注释 System.gc();
行,结果是肯定的:
add new element 1
add new element 2
add new element 3
add new element 4
add new element 5
[GC (Allocation Failure) --[PSYoungGen: 48344K->48344K(59904K)] 168344K->168352K(196608K), 0.0090913 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 48344K->41377K(59904K)] [ParOldGen: 120008K->120002K(136704K)] 168352K->161380K(196608K), [Metaspace: 5382K->5382K(1056768K)], 0.0380767 secs] [Times: user=0.09 sys=0.03, real=0.04 secs]
[GC (Allocation Failure) --[PSYoungGen: 41377K->41377K(59904K)] 161380K->161380K(196608K), 0.0040596 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 41377K->41314K(59904K)] [ParOldGen: 120002K->120002K(136704K)] 161380K->161317K(196608K), [Metaspace: 5382K->5378K(1056768K)], 0.0118884 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at test.DeadLock.main(DeadLock.java:23)
Heap
PSYoungGen total 59904K, used 42866K [0x00000000fbd80000, 0x0000000100000000, 0x0000000100000000)
eden space 51712K, 82% used [0x00000000fbd80000,0x00000000fe75c870,0x00000000ff000000)
from space 8192K, 0% used [0x00000000ff800000,0x00000000ff800000,0x0000000100000000)
to space 8192K, 0% used [0x00000000ff000000,0x00000000ff000000,0x00000000ff800000)
ParOldGen total 136704K, used 120002K [0x00000000f3800000, 0x00000000fbd80000, 0x00000000fbd80000)
object space 136704K, 87% used [0x00000000f3800000,0x00000000fad30b90,0x00000000fbd80000)
Metaspace used 5409K, capacity 5590K, committed 5760K, reserved 1056768K
class space used 576K, capacity 626K, committed 640K, reserved 1048576K
JVM 会自行调用 GC,但在这种情况下,为时已晚。
在这种情况下,负责清除内存的不仅仅是 GC。
Map 值是强可达的,并且在对其调用某些操作时由 map 本身清除。
如果您打开 GC 事件 (XX:+PrintGC),这是输出:
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.0123285 secs]
[GC (Allocation Failure) 2400920K->2400856K(2801664K), 0.0090720 secs]
[Full GC (Allocation Failure) 2400856K->2400805K(2590720K), 0.0302800 secs]
[GC (Allocation Failure) 2400805K->2400805K(2801664K), 0.0069942 secs]
[Full GC (Allocation Failure) 2400805K->2400753K(2620928K), 0.0146932 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
直到最后一次尝试将值放入映射时才会触发 GC。
WeakHashMap 无法清除陈旧的条目,直到映射键出现在引用队列上。
映射键在被垃圾回收之前不会出现在引用队列中。
在地图有任何机会清除自身之前触发新地图值的内存分配。
当内存分配失败并触发 GC 时,映射键确实会被收集。但为时已晚——没有足够的内存被释放来分配新的地图值。
如果减少有效负载,最终可能会有足够的内存来分配新的映射值,陈旧的条目将被删除。
另一种解决方案可能是将值本身包装到 WeakReference 中。这将允许 GC 清除资源,而无需等待地图自行完成。
这是输出:
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
.
下的默认值
如果我稍微改变一下你的程序,你认为会发生什么(运行 under jdk-8
with -Xmx20m
)
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
}
}
它将正常工作。这是为什么?因为它为您的程序提供了足够的喘息空间,以便在 WeakHashMap
清除其条目之前进行新的分配。另一个答案已经解释了这是如何发生的。
现在,在 G1GC
,情况会有所不同。当分配这么大的对象时(超过 1/2 MB 通常),这将被称为 humongous allocation
。当发生这种情况时,将触发 concurrent GC。作为该循环的一部分:将触发 young 集合并启动 Cleanup phase
负责将事件发布到 ReferenceQueue
,以便WeakHashMap
清除其条目。
所以对于这段代码:
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
}
}
我 运行 和 jdk-13(其中 G1GC
是默认值)
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
这已经做了一些不同的事情。它启动一个 concurrent cycle
(完成 ,而 你的应用程序是 运行ning),因为有一个 G1 Humongous Allocation
。作为此并发循环的一部分,它会执行一个年轻的 GC 循环(即 停止 您的应用程序,同时 运行ning)
[2.082s][info ][gc,start] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation)
作为 young GC 的一部分,它还会清除 humongous regions、here is the defect。
你现在可以看到,当分配真正的大对象时,jdk-13
并没有等待垃圾堆积在旧区域,而是触发了一个并发 GC循环,挽救了局面;不像 jdk-8。
您可能想阅读 DisableExplicitGC
and/or ExplicitGCInvokesConcurrent
的意思,再加上 System.gc
并理解为什么调用 System.gc
实际上有帮助。
如果不调用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();
}
}
如下,添加-XX:+PrintGCDetails
打印GC信息;如您所见,实际上,JVM 尝试执行完整的 GC 运行,但失败了;我还是不知道原因。很奇怪,如果我取消注释 System.gc();
行,结果是肯定的:
add new element 1
add new element 2
add new element 3
add new element 4
add new element 5
[GC (Allocation Failure) --[PSYoungGen: 48344K->48344K(59904K)] 168344K->168352K(196608K), 0.0090913 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 48344K->41377K(59904K)] [ParOldGen: 120008K->120002K(136704K)] 168352K->161380K(196608K), [Metaspace: 5382K->5382K(1056768K)], 0.0380767 secs] [Times: user=0.09 sys=0.03, real=0.04 secs]
[GC (Allocation Failure) --[PSYoungGen: 41377K->41377K(59904K)] 161380K->161380K(196608K), 0.0040596 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 41377K->41314K(59904K)] [ParOldGen: 120002K->120002K(136704K)] 161380K->161317K(196608K), [Metaspace: 5382K->5378K(1056768K)], 0.0118884 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at test.DeadLock.main(DeadLock.java:23)
Heap
PSYoungGen total 59904K, used 42866K [0x00000000fbd80000, 0x0000000100000000, 0x0000000100000000)
eden space 51712K, 82% used [0x00000000fbd80000,0x00000000fe75c870,0x00000000ff000000)
from space 8192K, 0% used [0x00000000ff800000,0x00000000ff800000,0x0000000100000000)
to space 8192K, 0% used [0x00000000ff000000,0x00000000ff000000,0x00000000ff800000)
ParOldGen total 136704K, used 120002K [0x00000000f3800000, 0x00000000fbd80000, 0x00000000fbd80000)
object space 136704K, 87% used [0x00000000f3800000,0x00000000fad30b90,0x00000000fbd80000)
Metaspace used 5409K, capacity 5590K, committed 5760K, reserved 1056768K
class space used 576K, capacity 626K, committed 640K, reserved 1048576K
JVM 会自行调用 GC,但在这种情况下,为时已晚。 在这种情况下,负责清除内存的不仅仅是 GC。 Map 值是强可达的,并且在对其调用某些操作时由 map 本身清除。
如果您打开 GC 事件 (XX:+PrintGC),这是输出:
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.0123285 secs]
[GC (Allocation Failure) 2400920K->2400856K(2801664K), 0.0090720 secs]
[Full GC (Allocation Failure) 2400856K->2400805K(2590720K), 0.0302800 secs]
[GC (Allocation Failure) 2400805K->2400805K(2801664K), 0.0069942 secs]
[Full GC (Allocation Failure) 2400805K->2400753K(2620928K), 0.0146932 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
直到最后一次尝试将值放入映射时才会触发 GC。
WeakHashMap 无法清除陈旧的条目,直到映射键出现在引用队列上。 映射键在被垃圾回收之前不会出现在引用队列中。 在地图有任何机会清除自身之前触发新地图值的内存分配。 当内存分配失败并触发 GC 时,映射键确实会被收集。但为时已晚——没有足够的内存被释放来分配新的地图值。 如果减少有效负载,最终可能会有足够的内存来分配新的映射值,陈旧的条目将被删除。
另一种解决方案可能是将值本身包装到 WeakReference 中。这将允许 GC 清除资源,而无需等待地图自行完成。 这是输出:
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
.
如果我稍微改变一下你的程序,你认为会发生什么(运行 under jdk-8
with -Xmx20m
)
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
}
}
它将正常工作。这是为什么?因为它为您的程序提供了足够的喘息空间,以便在 WeakHashMap
清除其条目之前进行新的分配。另一个答案已经解释了这是如何发生的。
现在,在 G1GC
,情况会有所不同。当分配这么大的对象时(超过 1/2 MB 通常),这将被称为 humongous allocation
。当发生这种情况时,将触发 concurrent GC。作为该循环的一部分:将触发 young 集合并启动 Cleanup phase
负责将事件发布到 ReferenceQueue
,以便WeakHashMap
清除其条目。
所以对于这段代码:
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
}
}
我 运行 和 jdk-13(其中 G1GC
是默认值)
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
这已经做了一些不同的事情。它启动一个 concurrent cycle
(完成 ,而 你的应用程序是 运行ning),因为有一个 G1 Humongous Allocation
。作为此并发循环的一部分,它会执行一个年轻的 GC 循环(即 停止 您的应用程序,同时 运行ning)
[2.082s][info ][gc,start] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation)
作为 young GC 的一部分,它还会清除 humongous regions、here is the defect。
你现在可以看到,当分配真正的大对象时,jdk-13
并没有等待垃圾堆积在旧区域,而是触发了一个并发 GC循环,挽救了局面;不像 jdk-8。
您可能想阅读 DisableExplicitGC
and/or ExplicitGCInvokesConcurrent
的意思,再加上 System.gc
并理解为什么调用 System.gc
实际上有帮助。