研究了Java17中
System.gc
的源码实现细节,发现System.gc
最终会触发两种GC类型:
full gc(停止世界),当我们使用 SerialGC、ParallelGC、ZGC 时。并且调用
System.gc
的 java 线程将被阻塞,直到完整的 gc 完成。
并发full gc,当我们使用G1、Shenandoah配合
-XX:+ExplicitGCInvokesConcurrent
时,java线程在触发并发full gc后很快就会从System.gc
返回。
现在我们讨论full gc的情况,因为在full gc完成之前,java线程总是会阻塞在本机代码中(安全点),为什么jdk在调用
Thread.sleep
之后又调用System.gc
?
我们真的需要 Thread.sleep 吗?
public class FileChannelImpl extends FileChannel
{
private Unmapper mapInternal(MapMode mode, long position, long size, int prot, boolean isSync)
throws IOException
{
try {
// If map0 did not throw an exception, the address is valid
addr = map0(prot, mapPosition, mapSize, isSync);
} catch (OutOfMemoryError x) {
// An OutOfMemoryError may indicate that we've exhausted
// memory so force gc and re-attempt map
System.gc();
try {
// do We really need Thread.sleep here ?
Thread.sleep(100);
} catch (InterruptedException y) {
Thread.currentThread().interrupt();
}
try {
addr = map0(prot, mapPosition, mapSize, isSync);
} catch (OutOfMemoryError y) {
// After a second OOME, fail
throw new IOException("Map failed", y);
}
}
}
}
java线程直到full gc完成后才会从
System.gc
返回,所以我觉得这里调用Thread.sleep
之后就没有必要再调用System.gc(
了
您的陈述“java线程将在触发并发full gc后立即从System.gc返回”与您的假设“java线程在full gc完成之前不会从System.gc返回”相矛盾。
触发并发垃圾回收并不意味着等待其完成。
但是,无论垃圾收集是否在从
System.gc()
返回之前完成,相关操作都不会等待垃圾收集完成,而是等待 cleaner 完成,这可能会释放现有的本机内存分配.
这款内部清洁剂的工作原理与现在公开的类似。它在与垃圾收集器不同的线程中运行,并等待垃圾收集器移交已发现的其指示对象变得无法访问的幻像引用。
因此,给清理线程足够的时间至关重要,以便有可能从等待状态更改为运行状态并执行清理操作,即释放本机内存(或释放文件映射)。