概述
JVM的GC一般情況下是JVM本身根據(jù)一定的條件觸發(fā)的,不過我們還是可以做一些人為的觸發(fā),比如通過jvmti做強制GC,通過System.gc觸發(fā),還可以通過jmap來觸發(fā)等,針對每個場景其實我們都可以寫篇文章來做一個介紹,本文重點介紹下System.gc的原理
或許大家已經(jīng)知道如下相關(guān)的知識
- system.gc其實是做一次full gc
- system.gc會暫停整個進程
- system.gc一般情況下我們要禁掉,使用-XX:+DisableExplicitGC
- system.gc在cms gc下我們通過-XX:+ExplicitGCInvokesConcurrent來做一次稍微高效點的GC(效果比Full GC要好些)
- system.gc最常見的場景是RMI/NIO下的堆外內(nèi)存分配等
如果你已經(jīng)知道上面這些了其實也說明你對System.gc有過一定的了解,至少踩過一些坑,但是你是否更深層次地了解過它,比如
- 為什么CMS GC下-XX:+ExplicitGCInvokesConcurrent這個參數(shù)加了之后會比真正的Full GC好?
- 它如何做到暫停整個進程?
- 堆外內(nèi)存分配為什么有時候要配合System.gc?
如果你上面這些疑惑也都知道,那說明你很懂System.gc了,那么接下來的文字你可以不用看啦
JDK里的System.gc的實現(xiàn)
先貼段代碼吧(java.lang.System)
/**
* Runs the garbage collector.
* <p>
* Calling the <code>gc</code> method suggests that the Java Virtual
* Machine expend effort toward recycling unused objects in order to
* make the memory they currently occupy available for quick reuse.
* When control returns from the method call, the Java Virtual
* Machine has made a best effort to reclaim space from all discarded
* objects.
* <p>
* The call <code>System.gc()</code> is effectively equivalent to the
* call:
* <blockquote><pre>
* Runtime.getRuntime().gc()
* </pre></blockquote>
*
* @see java.lang.Runtime#gc()
*/
public static void gc() {
Runtime.getRuntime().gc();
}
發(fā)現(xiàn)主要調(diào)用的是Runtime里的gc方法(java.lang.Runtime)
/**
* Runs the garbage collector.
* Calling this method suggests that the Java virtual machine expend
* effort toward recycling unused objects in order to make the memory
* they currently occupy available for quick reuse. When control
* returns from the method call, the virtual machine has made
* its best effort to recycle all discarded objects.
* <p>
* The name <code>gc</code> stands for "garbage
* collector". The virtual machine performs this recycling
* process automatically as needed, in a separate thread, even if the
* <code>gc</code> method is not invoked explicitly.
* <p>
* The method {@link System#gc()} is the conventional and convenient
* means of invoking this method.
*/
public native void gc();
這里看到gc方法是native的,在java層面只能到此結(jié)束了,代碼只有這么多,要了解更多,可以看方法上面的注釋,不過我們需要更深層次地來了解其實現(xiàn),那還是準備好進入到j(luò)vm里去看看
Hotspot里System.gc的實現(xiàn)
如何找到native里的實現(xiàn)
上面提到了Runtime.gc是一個本地方法,那需要先在jvm里找到對應的實現(xiàn),這里稍微提一下jvm里native方法最常見的也是最簡單的查找,jdk里一般含有native方法的類,一般都會有一個對應的c文件,比如上面的java.lang.Runtime這個類,會有一個Runtime.c的文件和它對應,native方法的具體實現(xiàn)都在里面了,如果你有source,可能會猜到和下面的方法對應
JNIEXPORT void JNICALL
Java_java_lang_Runtime_gc(JNIEnv *env, jobject this)
{
JVM_GC();
}
其實沒錯的,就是這個方法,jvm要查找到這個native方法其實很簡單的,看方法名可能也猜到規(guī)則了,Java_pkgName_className_methodName,其中pkgName里的"."替換成"_",這樣就能找到了,當然規(guī)則不僅僅只有這么一個,還有其他的,這里不細說了,有機會寫篇文章詳細介紹下其中細節(jié)
DisableExplicitGC參數(shù)
上面的方法里是調(diào)用JVM_GC(),實現(xiàn)如下
JVM_ENTRY_NO_ENV(void, JVM_GC(void))
JVMWrapper("JVM_GC");
if (!DisableExplicitGC) {
Universe::heap()->collect(GCCause::_java_lang_system_gc);
}
JVM_END
看到這里我們已經(jīng)解釋其中一個疑惑了,就是DisableExplicitGC
這個參數(shù)是在哪里生效的,起的什么作用,如果這個參數(shù)設(shè)置為true的話,那么將直接跳過下面的邏輯,我們通過-XX:+ DisableExplicitGC就是將這個屬性設(shè)置為true,而這個屬性默認情況下是true還是false呢
product(bool, DisableExplicitGC, false, \
"Tells whether calling System.gc() does a full GC")
ExplicitGCInvokesConcurrent參數(shù)
這里主要針對CMSGC下來做分析,所以我們上面看到調(diào)用了heap的collect方法,我們找到對應的邏輯
void GenCollectedHeap::collect(GCCause::Cause cause) {
if (should_do_concurrent_full_gc(cause)) {
#ifndef SERIALGC
// mostly concurrent full collection
collect_mostly_concurrent(cause);
#else // SERIALGC
ShouldNotReachHere();
#endif // SERIALGC
} 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
}
}
bool GenCollectedHeap::should_do_concurrent_full_gc(GCCause::Cause cause) {
return UseConcMarkSweepGC &&
((cause == GCCause::_gc_locker && GCLockerInvokesConcurrent) ||
(cause == GCCause::_java_lang_system_gc && ExplicitGCInvokesConcurrent));
}
collect里一開頭就有個判斷,如果should_do_concurrent_full_gc返回true,那會執(zhí)行collect_mostly_concurrent做并行的回收
其中should_do_concurrent_full_gc中的邏輯是如果使用CMS GC,并且是system gc且ExplicitGCInvokesConcurrent==true,那就做并行full gc,當我們設(shè)置-XX:+ ExplicitGCInvokesConcurrent的時候,就意味著應該做并行Full GC了,不過要注意千萬不要設(shè)置-XX:+DisableExplicitGC,不然走不到這個邏輯里來了
并行Full GC相對正常的Full GC效率高在哪里
stop the world
說到GC,這里要先提到VMThread,在jvm里有這么一個線程不斷輪詢它的隊列,這個隊列里主要是存一些VM_operation的動作,比如最常見的就是內(nèi)存分配失敗要求做GC操作的請求等,在對gc這些操作執(zhí)行的時候會先將其他業(yè)務線程都進入到安全點,也就是這些線程從此不再執(zhí)行任何字節(jié)碼指令,只有當出了安全點的時候才讓他們繼續(xù)執(zhí)行原來的指令,因此這其實就是我們說的stop the world(STW),整個進程相當于靜止了
CMS GC
這里必須提到CMS GC,因為這是解釋并行Full GC和正常Full GC的關(guān)鍵所在,CMS GC我們分為兩種模式background和foreground,其中background顧名思義是在后臺做的,也就是可以不影響正常的業(yè)務線程跑,觸發(fā)條件比如說old的內(nèi)存占比超過多少的時候就可能觸發(fā)一次background式的cms gc,這個過程會經(jīng)歷CMS GC的所有階段,該暫停的暫停,該并行的并行,效率相對來說還比較高,畢竟有和業(yè)務線程并行的gc階段;而foreground則不然,它發(fā)生的場景比如業(yè)務線程請求分配內(nèi)存,但是內(nèi)存不夠了,于是可能觸發(fā)一次cms gc,這個過程就必須是要等內(nèi)存分配到了線程才能繼續(xù)往下面走的,因此整個過程必須是STW的,因此CMS GC整個過程都是暫停應用的,但是為了提高效率,它并不是每個階段都會走的,只走其中一些階段,這些省下來的階段主要是并行階段,Precleaning、AbortablePreclean,Resizing這幾個階段都不會經(jīng)歷,其中sweep階段是同步的,但不管怎么說如果走了類似foreground的cms gc,那么整個過程業(yè)務線程都是不可用的,效率會影響挺大。CMS GC具體的過程后面再寫文章詳細說,其過程確實非常復雜的
正常的Full GC
正常的Full GC其實是整個gc過程包括ygc和cms gc(這里說的是真正意義上的Full GC,還有些場景雖然調(diào)用Full GC的接口,但是并不會都做,有些時候只做ygc,有些時候只做cms gc)都是由VMThread來執(zhí)行的,因此整個時間是ygc+cms gc的時間之和,其中CMS GC是上面提到的foreground式的,因此整個過程會比較長,也是我們要避免的
并行的Full GC
并行Full GC也通樣會做YGC和CMS GC,但是效率高就搞在CMS GC是走的background的,整個暫停的過程主要是YGC+CMS_initMark+CMS_remark幾個階段
堆外內(nèi)存常配合使用System GC
這里說的堆外內(nèi)存主要針對java.nio.DirectByteBuffer,這些對象的創(chuàng)建過程會通過Unsafe接口直接通過os::malloc來分配內(nèi)存,然后將內(nèi)存的起始地址和大小存到j(luò)ava.nio.DirectByteBuffer對象里,這樣就可以直接操作這些內(nèi)存。這些內(nèi)存只有在DirectByteBuffer回收掉之后才有機會被回收,因此如果這些對象大部分都移到了old,但是一直沒有觸發(fā)CMS GC或者Full GC,那么悲劇將會發(fā)生,因為你的物理內(nèi)存被他們耗盡了,因此為了避免這種悲劇的發(fā)生,通過-XX:MaxDirectMemorySize來指定最大的堆外內(nèi)存大小,當使用達到了閾值的時候?qū)⒄{(diào)用System.gc來做一次full gc,以此來回收掉沒有被使用的堆外內(nèi)存,具體堆外內(nèi)存是如何回收的,其原理機制又是怎樣的,還是后面寫篇詳細的文章吧