System.gc()
介绍
System.gc()
,是JDK提供的触发Full GC的一种方式,会触发Full GC,其间会stop the world,对业务影响较大,一般情况下不会直接使用。
system.gc
其实是做一次full gc;system.gc
会暂停整个进程;system.gc
一般情况下我们要禁掉,使用-XX:+DisableExplicitGC
;system.gc
在cms gc下我们通过-XX:+ExplicitGCInvokesConcurrent
来做一次稍微高效点的GC(效果比Full GC要好些);system.gc
最常见的场景是RMI/NIO下的堆外内存分配等;源码
JDK实现
System.gc()
1
2
3
public static void gc() {
Runtime.getRuntime().gc();
}
Runtime.gc()
1
2
//java本地方法
public native void gc();
JVM实现
Runtime.c#Java_java_lang_Runtime_gc()
1
2
3
4
5
JNIEXPORT void JNICALL
Java_java_lang_Runtime_gc(JNIEnv *env, jobject this)
{
JVM_GC();
}
jvm.cpp#JVM_GC()
1
2
3
4
5
6
7
JVM_ENTRY_NO_ENV(void, JVM_GC(void))
JVMWrapper("JVM_GC");
//对应配置-XX:+DisableExplicitGC
if (!DisableExplicitGC) {
Universe::heap()->collect(GCCause::_java_lang_system_gc);
}
JVM_END
由heap()调用collect方法,具体是哪种heap,需要看gc算法,如常用的CMS GC的对应的heap是GenCollectedHeap
。
genCollectedHeap.cpp#collect()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void GenCollectedHeap::collect(GCCause::Cause cause) {
//判断是否是并发Full GC
if (should_do_concurrent_full_gc(cause)) {
#if INCLUDE_ALL_GCS
// mostly concurrent full collection
//进行并发Full GC
collect_mostly_concurrent(cause);
#else // INCLUDE_ALL_GCS
ShouldNotReachHere();
#endif // INCLUDE_ALL_GCS
} else {
#ifdef ASSERT
if (cause == GCCause::_scavenge_alot) {
// minor collection only
collect(cause, 0);
} else {
// Stop-the-world full collection
collect(cause, n_gens() - 1);
}
#else
// Stop-the-world full collection
collect(cause, n_gens() - 1);
#endif
}
}
genCollectedHeap.cpp#should_do_concurrent_full_gc()
1
2
3
4
5
6
//判断是不是进行一次并发Full GC
bool GenCollectedHeap::should_do_concurrent_full_gc(GCCause::Cause cause) {
return UseConcMarkSweepGC &&
((cause == GCCause::_gc_locker && GCLockerInvokesConcurrent) ||
(cause == GCCause::_java_lang_system_gc && ExplicitGCInvokesConcurrent));
}
如果当前是CMS垃圾收集器,且由System.gc()
引起,且设置-XX:ExplicitGCInvokesConcurrent
参数返回true。
涉及-XX:ExplicitGCInvokesConcurrent
参数配置。
使用
堆外内存常配合使用System.gc()
堆外内存主要针对java.nio.DirectByteBuffer
,这些对象的创建过程会通过Unsafe接口直接通过os::malloc
来分配内存,然后将内存的起始地址和大小存到java.nio.DirectByteBuffer
对象里,这样就可以直接操作这些内存。这些内存只有在DirectByteBuffer`回收掉之后才有机会被回收,因此如果这些对象大部分都移到了old,但是一直没有触发CMS GC或者Full GC,那么悲剧将会发生,因为你的物理内存被他们耗尽了,因此为了避免这种悲剧的发生,通过-XX:MaxDirectMemorySize来指定最大的堆外内存大小,当使用达到了阈值的时候将调用System.gc来做一次full gc,以此来回收掉没有被使用的堆外内存。
总结
System.gc()
会触发Full GC,可以通过-XX:+DisableExplicitGC
参数屏蔽System.gc()
,在使用CMS GC的前提下,也可以使用-XX:+ExplicitGCInvokesConcurrent
参数来进行并发Full GC,提升性能。
面试题
在 Java 中,如何手动的让 GC 进行垃圾回收?
- System.gc()
jmap -dump:live
或jmap -histo:live
System.gc 是 Full Gc 吗?
System.gc
其实是做一次 full gc。这一点可以根据 DisableExplicitGC
的注释说明来看。
System.gc 的危害是什么?
System.gc
是一个 Full gc,所以会暂停整个进程。如果进程经常被频繁暂停,就要注意超时、并发等问题。
如何避免 System.gc ?
- 通过
-XX:+DisableExplicitGC
禁掉 System.gc。 - 如果是CMS垃圾收集器可以使用
-XX:+ExplicitGCInvokesConcurrent
参数使Full GC变为并发CMS GC。
System.gc 的 Full Gc 如何做到暂停整个进程?
Java 里面的 GC 有一个重要的线程 VMThread。在 jvm 里,这个线程会不断轮询它的队列,这个队列里主要是存一些 VM_operation 的动作,比如最常见的就是内存分配失败要求做 GC 操作的请求等,在对 gc 这些操作执行的时候会先将其他业务线程都进入到安全点,也就是这些线程从此不再执行任何字节码指令,只有当出了安全点的时候才让他们继续执行原来的指令,因此这其实就是我们说的 stop the world(STW),整个进程相当于静止了。
并行的 Full GC 是否有了解?
并行 Full GC 也同样会做 YGC 和 CMS GC,但是效率高就搞在 CMS GC 是走的 background 的,整个暂停的过程主要是 YGC、CMS_initMark、CMS_remark
几个阶段。
background 顾名思义是在后台做的,也就是可以不影响正常的业务线程跑,触发条件比如说 old 的内存占比超过多少的时候就可能触发一次 background 式的 cms gc,这个过程会经历 CMS GC 的所有阶段,该暂停的暂停,该并行的并行,效率相对来说还比较高,毕竟有和业务线程并行的 gc 阶段;
而 foreground 则不然,它发生的场景比如业务线程请求分配内存,但是内存不够了,于是可能触发一次 cms gc,这个过程就必须是要等内存分配到了线程才能继续往下面走的,因此整个过程必须是STW的,因此 CMS GC 整个过程都是暂停应用的,但是为了提高效率,它并不是每个阶段都会走的,只走其中一些阶段,这些省下来的阶段主要是并行阶段。