Dom*_*eng 10 java garbage-collection weak-references out-of-memory java-8
如果不调用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();
}
}
Run Code Online (Sandbox Code Playgroud)
如下,添加-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
Run Code Online (Sandbox Code Playgroud)
JVM 会自行调用 GC,但在这种情况下,它会为时已晚。在这种情况下,不仅仅是 GC 负责清除内存。地图值是强可达的,并且在对其调用某些操作时由地图本身清除。
如果您打开 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
Run Code Online (Sandbox Code Playgroud)
直到最后一次尝试将值放入地图时才会触发 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)
Run Code Online (Sandbox Code Playgroud)
好多了。
另一个答案确实是正确的,我编辑了我的。作为一个小附录,G1GC不会表现出这种行为,不像ParallelGC; 这是java-8.
如果我将您的程序稍微更改为(在jdk-8with下运行-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
}
}
Run Code Online (Sandbox Code Playgroud)
它会工作得很好。这是为什么?因为它为您的程序提供了足够的喘息空间,以便在WeakHashMap清除其条目之前进行新的分配。另一个答案已经解释了这是如何发生的。
现在,在G1GC,事情会有点不同。当分配如此大的对象时(通常超过 1/2 MB ),这将被称为humongous allocation. 当发生这种情况时,将触发并发GC。作为该循环的一部分:将触发一个年轻的集合并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
}
}
Run Code Online (Sandbox Code Playgroud)
我用 jdk-13 运行(G1GC默认值在哪里)
java -Xmx20m "-Xlog:gc*=debug" gc.WeakHashMapTest
Run Code Online (Sandbox Code Playgroud)
以下是部分日志:
[2.082s][debug][gc,ergo] Request concurrent cycle initiation (requested by GC cause). GC cause: G1 Humongous Allocation
Run Code Online (Sandbox Code Playgroud)
这已经做了一些不同的事情。它开始concurrent cycle(在您的应用程序运行时完成),因为有一个G1 Humongous Allocation. 作为这个并发周期的一部分,它会执行一个年轻的 GC 周期(在运行时停止您的应用程序)
[2.082s][info ][gc,start] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation)
Run Code Online (Sandbox Code Playgroud)
作为年轻GC 的一部分,它还清除了巨大的区域,这是缺陷。
您现在可以看到,jdk-13当分配真正的大对象时,不会等待垃圾在旧区域堆积,而是触发并发GC 循环,从而节省了一天;与 jdk-8 不同。
您可能想阅读什么DisableExplicitGC和/或ExplicitGCInvokesConcurrent意思,再加上System.gc并理解为什么呼叫System.gc实际上在这里有帮助。
| 归档时间: |
|
| 查看次数: |
321 次 |
| 最近记录: |