相信大多數人的理解是Major GC只針對老年代,Full GC會先觸發一次Minor GC,不知對否?我參考了R大的分析和介紹,總結了一下相關的說明和分析結論。
針對HotSpot VM的實現,它裡面的GC其實準確分類只有兩大種:
Partial GC代表著並不收集整個GC堆的模式
Full GC代表著收集整個JVM的執行時堆+方法區+直接堆外記憶體的總體範圍內。(甚至可以理解為JVM程序範圍內的絕大部分範圍的資料區域)。
它會涵蓋了所有的模式和區域包含:Young Gen(新生代)、Tenured Gen(老生代)、Perm/Meta Gen(元空間)(JDK8前後的版本)等全域性範圍的GC垃圾回收模式。
在一般情況下Major GC通常是跟Full GC是等價的,收集整個GC堆。但如果從HotSpot VM底層的細節出發,如果再有人說「Major GC」的時候一定要問清楚他想要指的是上面的Full GC還是Old/Tenured GC。
按HotSpot VM的Serial GC的實現來看,當Young gen中的Eden區分達到閾值(屬於一定的百分比進行控制)的時候觸發。
注意:Young GC中有部分存活物件會晉升到Old/Tenured Gen,所以Young GC後Old Gen的佔用量通常會有所升高。
當準備要觸發一次Young GC時,如果發現統計資料說之前Young Old/Tenured Gen剩餘的空間大,則不會觸發Young GC,而是轉為觸發Full GC(因為HotSpot VM的GC裡,除了CMS的Concurrent collection之外,其它能收集Old/Tenured Gen的GC都會同時收集整個GC堆,包括Young gen,所以不需要事先觸發一次單獨的Young GC);
如果有Perm/Meta gen的話,要在Perm/Meta gen分配空間但已經沒有足夠空間時,也要觸發一次full GC。
System.gc()方法或者Heap Dump自帶的GC,預設也是觸發Full GC。HotSpot VM裡其它非並行GC的觸發條件複雜一些,不過大致的原理與上面說的其實一樣。
注意:Parallel Scavenge(-XX:+UseParallelGC)框架下,預設是在要觸發Full GC前先執行一次Young GC,並且兩次GC之間能讓應用程式稍微執行一小下,以期降低Full GC的暫停時間(因為young GC會盡量清理了Young Gen的垃圾物件,減少了Full GC的掃描工作量)。控制這個行為的VM引數是-XX:+ScavengeBeforeFullGC。
Concurrent GC的觸發條件就不太一樣。以CMS GC為例,它主要是定時去檢查Old Gen的使用量,當使用量超過了觸發比例就會啟動一次CMS GC,對Old gen做並行收集。
在Hotspot JVM實現的Serial GC, Parallel GC, CMS, G1 GC中大致可以對應到某個Young GC和Old GC演演算法組合;
很多人都見過JVM調優建議裡使用這個引數,對吧?但是為什麼要用它,什麼時候應該用而什麼時候用了會掉坑裡呢?
首先,要了解的是這個引數的作用。在Oracle/Sun JDK這個具體實現上,System.gc()的預設效果是引發一次stop-the-world的Full GC,由上面所知就是針對於整個GC堆做記憶體垃圾收集。
再次,如果採用了用了-XX:+DisableExplicitGC引數後,System.gc()的呼叫就會變成一個空呼叫,完全不會觸發任何GC(但是「函數呼叫」本身的開銷還是存在的哦~)。
java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:633)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:98)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
import java.nio.*;
public class DisableExplicitGCDemo {
public static void main(String[] args) {
for (int i = 0; i < 100000; i++) {
ByteBuffer.allocateDirect(128);
}
System.out.println("Done");
}
}
然後編譯、執行。
$ java -version
java version "1.6.0_25"
Java(TM) SE Runtime Environment (build 1.6.0_25-b06)
Java HotSpot(TM) 64-Bit Server VM (build 20.0-b11, mixed mode)
$ javac DisableExplicitGCDemo.java
$ java -XX:MaxDirectMemorySize=10m -XX:+PrintGC -XX:+DisableExplicitGC DisableExplicitGCDemo
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:633)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:98)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
at DisableExplicitGCDemo.main(DisableExplicitGCDemo.java:6)
$ java -XX:MaxDirectMemorySize=10m -XX:+PrintGC DisableExplicitGCDemo
[GC 10996K->10480K(120704K), 0.0433980 secs]
[Full GC 10480K->10415K(120704K), 0.0359420 secs]
Done
可以看到,同樣的程式,不帶-XX:+DisableExplicitGC
時能正常完成執行,而帶上這個引數後卻出現了OOM。-XX:MaxDirectMemorySize=10m限制了DirectByteBuffer能分配的空間的限額,以便問題更容易展現出來。不用這個引數就得多跑一會兒了。
迴圈不斷申請DirectByteBuffer但並沒有參照,所以這些DirectByteBuffer應該剛建立出來就已經滿足被GC的條件,等下次GC執行的時候就應該可以被回收。
實際上卻沒這麼簡單。DirectByteBuffer是種典型的「冰山」物件,也就是說它的Java物件雖然很小很無辜,但它背後卻會關聯著一定量的native memory資源,而這些資源並不在GC的控制之下,需要自己注意控制好。
對JVM如何使用native memory不熟悉的同學可以研究一下這篇演講,「Where Does All the Native Memory Go」。
Oracle/Sun JDK的實現裡,DirectByteBuffer有幾處值得注意的地方。
"A cleaner tracks a referent object and encapsulates a thunk of arbitrary cleanup code. Some time after the GC detects that a cleaner's referent has become phantom-reachable, the reference-handler thread will run the cleaner."
/**
* General-purpose phantom-reference-based cleaners.
*
* <p> Cleaners are a lightweight and more robust alternative to finalization.
* They are lightweight because they are not created by the VM and thus do not
* require a JNI upcall to be created, and because their cleanup code is
* invoked directly by the reference-handler thread rather than by the
* finalizer thread. They are more robust because they use phantom references,
* the weakest type of reference object, thereby avoiding the nasty ordering
* problems inherent to finalization.
*
* <p> A cleaner tracks a referent object and encapsulates a thunk of arbitrary
* cleanup code. Some time after the GC detects that a cleaner's referent has
* become phantom-reachable, the reference-handler thread will run the cleaner.
* Cleaners may also be invoked directly; they are thread safe and ensure that
* they run their thunks at most once.
*
* <p> Cleaners are not a replacement for finalization. They should be used
* only when the cleanup code is extremely simple and straightforward.
* Nontrivial cleaners are inadvisable since they risk blocking the
* reference-handler thread and delaying further cleanup and finalization.
*
*
* @author Mark Reinhold
* @version %I%, %E%
*/
Oracle/Sun JDK中的HotSpot VM只會在Old Gen GC(Full GC/Major GC或者Concurrent GC都算)的時候才會對Old Gen中的物件做Reference Processing,而在Young GC/Minor GC時只會對Young Gen裡的物件做Reference processing。Full GC會對Old Gen做Reference processing,進而能觸發Cleaner對已死的DirectByteBuffer物件做清理工作。
如果很長一段時間裡沒做過GC或者只做了Young GC的話則不會在Old Gen觸發Cleaner的工作,那麼就可能讓本來已經死了的、但已經晉升到Old Gen的DirectByteBuffer關聯的Native Memory得不到及時釋放。
為DirectByteBuffer分配空間過程中會顯式呼叫System.gc(),以通過Full GC來強迫已經無用的DirectByteBuffer物件釋放掉它們關聯的native memory。
// These methods should be called whenever direct memory is allocated or
// freed. They allow the user to control the amount of direct memory
// which a process may access. All sizes are specified in bytes.
static void reserveMemory(long size) {
synchronized (Bits.class) {
if (!memoryLimitSet && VM.isBooted()) {
maxMemory = VM.maxDirectMemory();
memoryLimitSet = true;
}
if (size <= maxMemory - reservedMemory) {
reservedMemory += size;
return;
}
}
System.gc();
try {
Thread.sleep(100);
} catch (InterruptedException x) {
// Restore interrupt status
Thread.currentThread().interrupt();
}
synchronized (Bits.class) {
if (reservedMemory + size > maxMemory)
throw new OutOfMemoryError("Direct buffer memory");
reservedMemory += size;
}
}
這幾個實現特徵使得Oracle/Sun JDK依賴於System.gc()觸發GC來保證DirectByteMemory的清理工作能及時完成。
如果開啟了-XX:+DisableExplicitGC,清理工作就可能得不到及時完成,於是就有機會見到direct memory的OOM,也就是上面的例子演示的情況。我們這邊在實際生產環境中確實遇到過這樣的問題。
如果你在使用Oracle/Sun JDK,應用裡有任何地方用了direct memory,那麼使用-XX:+DisableExplicitGC要小心。如果用了該引數而且遇到direct memory的OOM,可以嘗試去掉該引數看是否能避開這種OOM。如果擔心System.gc()呼叫造成Full GC頻繁,可以嘗試下面提到 -XX:+ExplicitGCInvokesConcurrent 引數
本文來自部落格園,作者:洛神灬殤,轉載請註明原文連結:https://www.cnblogs.com/liboware/p/16986641.html,任何足夠先進的科技,都與魔法無異。