一、執行引擎
##一、執行引擎概述如果想讓一個java程式執行起來,執行引擎的任務就是將位元組碼指令解釋/編譯為對應平臺上的本地機器指令才可以。簡單來說,JVM中的執行引擎充當了將改機語言翻譯為機器語言的譯者。##二、執行引擎的工作過程1)執行引擎在執行的過程中究竟需要執行什麼樣的位元組碼指令完全依賴於PC暫存器2)每當執行完一項操作後,PC暫存器就會更新下一條需要被執行的指令地址3)當方法在執行的過程中,執行引擎有可能會透過儲存在區域性變量表中的物件引用準確定位到儲存在Java堆區中的物件例項資訊,以及透過物件頭中的元資料指標定位到目標物件的型別資訊。
##三、什麼是直譯器,什麼是JIT編譯器?直譯器:當java虛擬機器啟動時會根據預定義的規範對位元組碼採用逐行解釋的方式執行,將每條位元組碼檔案中的內容翻譯為對應平臺的本地機器指令執行。(解釋執行)JIT編譯器:就是虛擬機器將原始碼直接編譯成和本地機器平臺相關的語言。(先翻譯好,放在方法區等待執行)##四、直譯器JVM設計者初衷僅僅只是單純地為了滿足java程式實現跨平臺的特性,因此避免了採用靜態編譯的方式直接生成本地機器指令,從而誕生了實現直譯器在執行時採用逐行解釋位元組碼執行程式的想法。直譯器真正意義上所承擔的角色是一個執行時翻譯者,將位元組碼檔案中的內容翻譯為對應平臺的本地機器指令執行。當一條位元組碼指令被解釋執行完成後,接著在根據PC暫存器中記錄的下一條需要被執行的位元組碼指令執行解釋操作##五、JIT編譯器基於直譯器執行已經淪為低效的代名詞,為了解決這個問題,JVM平臺支援一種叫做即時編譯器的技術。即使編譯的目的是避免函式被解釋執行,而是將整個函式體編譯成為機器碼,每次函式執行時,只執行編譯後的機器碼即可,這種方式可以使執行效率大幅提升。##六、HotSpot為何直譯器和JIT編譯器共存?首先,在程式啟動後,直譯器可以馬上發揮作用,省去編譯的時間,立即執行。編譯器要想發揮作用,把程式碼編譯成原生代碼,需要一定的執行時間。但編譯為原生代碼後,執行效率高。所以,當JAVA虛擬機器啟動時,直譯器可以首先發揮作用,而不必等待即時編譯器全部編譯完成後再執行,這樣可以省去許多不必要的編譯時間。隨著時間的推移,編譯器發揮作用,把越來越多的程式碼編譯成原生代碼,獲得更高的執行效率。同時,解釋執行在編譯器進行激進最佳化不成立的時候,作為編譯器的逃生門。##七、熱點程式碼及探測方式程式碼是否需要啟動JIT編譯器將位元組碼直接便以為對應平臺的本地機器指令則需要根據程式碼被呼叫的頻率而定。哪些需要被編譯為原生代碼的位元組碼被稱為熱點程式碼。目前HotSpot VM所採用的的熱點探測方式是基於計數器的熱點探測:方法呼叫計數器,client模式下是1500次,server模式下是10000次才會觸發JIT編譯(不是按呼叫絕對次數統計的,呼叫次數有熱度衰減)。回邊計數器,統計一個方法中迴圈體程式碼執行的次數,在位元組碼中遇到控制流向後跳轉的指令成為回邊。##八、設定HotSpot模式-Xint完全採用直譯器模式執行程式-Xcomp完全採用JIT編譯器模式執行程式。如果即時編譯器出現問題,直譯器會介入執行。-Xmixed採用直譯器+即時編譯器的混合模式共同執行程式。
二、StringTable##一、String的基本特性·String字元創:例項化方式 String s1 = "aaa";//字面量的定義方式 String s2 = new String("bbb");·String是被宣告為final的,不可被繼承·String實現了Serializable介面,表示字串是支援序列化的 String實現了Comparable介面,表示String可以比較大小·String在JDK8及之前內部定義了final char[] value用於儲存字串資料 String在JDK9改為了final byte[] value·String代表不可變的字元序列。簡稱不可變性>當對字串重新賦值時,需要重新制定記憶體區域賦值,不能使用原有的value進行賦值。>當對現有的字串進行連線操作時,也需要重新制定記憶體區域賦值,不能使用原有的value進行賦值。>當呼叫String的replace()方法修改指定字元或字串是,也需要重新指定記憶體區域賦值,不能使用原有的value進行復制。·透過字面量的方式(區別於new)給一個字串賦值,此時的字串值宣告在字串常量池中。【字串常量池中是不可以放相同的字串的】eg:public class StringExer{ String str = "good"; char[] ch = {'t','e','s','t'}; public void change(String str,char ch[]){ str = "test ok"; ch[0] = 'b'; } public static void main(String[] args){ StringExer ex = new String Exer(); ex.change(str,ch); System.out.println(ex.str);//結果是good System.out.println(ex.ch);//結果是best }}##二、StringTable底層Hashtable結構的說明·字串常量池中是不會儲存相同內容的字串的>String的String Pool是一個固定大小的Hashtable,預設值大小長度是1009.如果放進String Pool的String非常多,就會造成Hash衝突嚴重,從而導致連結串列會很長,而連結串列長了之後直接回造成的影響就是當呼叫String.intern的效能會大幅下降>使用-XX:StringTableSize可以設定StringTable的長度>jdk6中StringTable是固定的,就是1009的長度,所以如果常量池中的字串過多就會導致效率下降很快,StringTableSize設定沒有要求>jdk7中,StringTable的長度預設值為60013>jdk8中,StringTabledSize 1009是可設定的最小值##三、String的記憶體分配·在Java中有8中基本資料型別和一種比較特殊的型別String。這些型別為了使它們在執行過程中速度更快‘更節省記憶體,都提供了一種常量池的概念·常量池就類似一個Java系統級別提供的快取。8中基本資料型別的常量池都是系統協調的,String型別的常量池比較特殊。它的主要使用方法有兩種。>直接使用""宣告出來的String物件會直接儲存在常量池中String info = "Hebei";>如果不是使用""宣告的String物件,可以使用String提供的intern()方法。·JDK6及以前,字串常量池存放在永久代jdk7字串常量池的位置調整到了java堆中JDK8永久代變為了元空間,字串常量池還在堆中##四、String的拼接操作·常量與常量的拼接結果在常量池,原理是編譯期最佳化·常量池中不會存在相同內容的常量·拼接操作中只要其中一個是變數,結果就在堆中(不是常量池中)。 變數拼接的原理是StringBuilder(相當於新new了一個物件)·拼接符號左右兩邊都是字串常量或常量引用,則仍然使用編譯期最佳化,即非StringBuilder的方式·如果拼接的結果呼叫intern()方法,則主動將常量池中還沒有的字串物件放入池中,並返回此物件地址,有的話則直接返回地址。說明:equals判斷兩個變數或者例項指向同一個記憶體空間的"值"是不是相同而==是判斷兩個變數或者例項是不是指向同一個記憶體空間地址eg:public void test1(){ String s1 = "a"+"b"+"c";//也放在常量池中 String s2 = "abc";//放在字串常量池中,並將此地址賦值給s2 System.out.println(s1==s2);//true System.out.println(s1.equals(s2));//true}public void test2(){ String s1 = "javaEE"; String s2 = "hadoop"; String s3 = "javaEEhadoop"; String s4 = "javaEE"+"hadoop"; String s5 = s1 + "hadoop"; String s6 = "javaEE" + s2; String s7 = s1 + s2; System.out.println(s3==s4);//true System.out.println(s3==s5);//false s1為變數,結果在堆 //如果拼接符號的前後出現了變數 //相當於在堆空間中new String() //具體的內容為拼接的結果 System.out.println(s3==s6);//false 結果在堆 System.out.println(s3==s7);//false 結果在堆 System.out.println(s5==s6);//false 結果在堆 System.out.println(s5==s7);//false 結果在堆 System.out.println(s6==s7);//false 結果在堆 //intern():判斷字串常量池中費否存在javaEEhadoop //如果存在,則返回常量池中JavaEEHadoop的地址 //如果字串常量池中不存在JavaEEHadoop,則在常量池中載入一份 //並返回此物件的地址 String s8 = s6.intern(); System.out.println(s3==s8);//true 結果在字串常量池中}public void test3(){ String s1 = "a"; String s2 = "b"; String s3 = "ab"; //先StringBuilder s = new StringBuilder() //然後 s.append("a") //然後 s.append("b") //然後 s.toString() --> 約等於new String("ab") //注:jdk5之後使用StringBuilder,jdk5之前使用StringBuffer String s4 = s1 + s2;// System.out.println(s3==s4);//false s4結果在堆}public void test4(){ final String s1 = "a";//常量 final String s2 = "b";//常量 String s3 = "ab"; String s4 = s1 + s2; System.out.println(s3==s4);//true}##五、String拼接和StringBuilder append的效率對比string拼接:for(int i=0;i<10000;i++){ s = s + "a"; //每次迴圈都會建立一個StringBuilder,然後再轉成String } StringBuilder append:for(int i=0;i<10000;i++){ s.append("a"); }結論:透過StringBuilder的append()的方式拼接字串的效率遠高於String的字串拼接方式。①、StringBuilder自始至終只建立了一個StringBuilder的物件②、使用String拼接方式,每次for迴圈都會建立一次StringBuilder和String物件,因此記憶體佔用也更大,GC還需要花費額外的時間。StringBuilder在實際開發中,如果基本確定字串的總長度不會高於highlevel的情況下,也減一使用構造器new StringBuilder(highLevel)//指定長度,防止在拼接的過程中StringBuilder擴容(擴容的過程中原有的StringBuilder會廢棄然後重新建立StringBuilder)。##六、intern()的使用如果不是""宣告的String物件,可以使用String提供的intern方法:intern方法會從字串常量池中查詢當前字串是否存在,若不存在就會將當前字串放入常量池中,然後返回地址;若存在則直接返回地址。eg:String info = new String("i love you").intern();也就是說,如果任意字串上呼叫String.intern()方法,name其返回結果所指向的那個實力,必須和直接以常量形式出現的字串實力完全相擁。因此下列表達式的值必定是true:eg: ("a"+"b"+"c").intern() == "abc";通俗點將,intern就是確保字串在記憶體裡只有一份複製,這樣可以節約記憶體空間,加快字串操作任務的執行速度。注意:這個值會被存放子啊字串常量池中。##七、new String("ab")建立了幾個物件? String str = new String("ab") 建立了兩個物件,一個new關鍵字在堆空間中建立的, 然後在字串常量池中建立了一個ab String str = new String("a")+new String("b"); 建立了3個物件, 物件1:new StringBuilder(),用以拼接 物件2: new String("a") 物件3:字串常量池中 a 物件4 new String("b") 物件5 字串常量池中b [stringbuilder的tostring方法是沒有在字串常量池中建立ab的]eg:public static void main(String[] args) { String s1 = new String("77");//s1指向堆中的地址,且常量池中有77 s1.intern();//指向了常量池中已有的77,但沒有把 這個引用賦值給s1,它是不同於s1=s1.intern()的 String s2 = new String("77").intern();//s2指向了s1在常量池中建立的77的地址 String s3 = "77";//s3也指向了s1在常量池中建立的77的地址 System.out.println(s1==s2);//false System.out.println(s1==s3);//false System.out.println(s2==s3);//true String s4 = new String("1")+new String("2");//s4指向了自己在堆中的地址,且在常量池中建立了1和2,但沒有建立12 s4.intern();//在常量池中建立了77,常量池中的77指向了s4在堆中的地址 String s5 = "12";//s5指向了常量池中77,相當於指向了s4的堆中的引用 System.out.println(s4==s5);//true String s6 = new String("5")+new String("6");//s6指向了自己在堆中的地址,且在常量池中建立了5和6,但沒有建立56 String s7 = "56";//在常量池中建立了56 String s8 = s6.intern();//intern由於56已在常量池中建立,因此s8指向了s7在常量池中建立的56 System.out.println(s6==s7);//false System.out.println(s6==s8);//false System.out.println(s7==s8);//true}intern()總結:·jdk1.6:>如果串池中有,則不會放入。返回已有的串池中的物件的地址。>如果串池沒有,則會把此物件複製一份,放入串池,並返回串池中的物件地址。·jdk1.7之後:>如果串池中有,則不會放入。返回已有的串池中的物件的地址。>如果串池沒有,則會把此物件的引用地址複製一份,放入串池,並返回串池中的引用地址[String str = new String("abc");][以上動作常量池中會存在abc,可以拆解開理解:常量池中建立abc,在堆中new一個String("abc"),棧中會儲存str,並將堆中的地址賦值給str]
三、垃圾回收1、垃圾回收概述##一、什麼是垃圾?·在執行程式中沒有任何指標指向的物件,這個物件就是需要被回收的垃圾##二、為什麼需要GC?如果不及時對記憶體中的垃圾進行清理,那麼,這些垃圾物件所佔的記憶體空間會一致保留到應用程式結束,被保留的空間無法被其它物件使用。甚至可能導致記憶體溢位。##三、記憶體溢位和記憶體洩漏·指程式申請記憶體時,沒有足夠的記憶體供申請者使用,或者說,給了你一塊儲存int型別資料的儲存空間,但是你卻儲存long型別的資料,那麼結果就是記憶體不夠用,此時就會報錯OOM,即所謂的記憶體溢位。·是指程式在申請記憶體後,無法釋放已申請的記憶體空間,一次記憶體洩漏似乎不會有大的影響,但記憶體洩漏堆積後的後果就是記憶體溢位。【GC的主要作用區域:方法區+堆】【Java堆是GC的工作重點】【頻繁回收 新生代,較少回收 老年代,基本不動 元空間或永久代】
2、垃圾回收相關演算法(重)2.1、垃圾標記階段的演算法:
##1、垃圾標記階段:物件存活判斷·在堆裡存放著幾乎所有的java物件例項,在GC執行垃圾回收之前,首先需要區分出記憶體中哪些是存活物件,哪些是已經死亡的物件。只有被標記為已經死亡的物件,GC才會在執行垃圾回收時,釋放掉其所佔用的記憶體空間,因此這個過程我們可以成為垃圾標記階段。·在JVM中如何標記一個死亡物件?簡單來說,當一個物件已經不再被任何的存活物件繼續引用時,就可以宣判為已經死亡。·判斷物件存活一般有兩種方式:【"引用計數演算法"】 和 【"可達性分析演算法"】##2、引用計數演算法Refrence Counting--->HotPot沒有使用·引用計數演算法,為每個物件儲存一個整型的"引用計數器"屬性們,用於記錄物件被引用的情況。·對於一個物件A,只要有任何一個物件引用了A,則A的引用計數器就加1;當引用失效時,引用計數器就減1.只要物件A的引用計數器的值為0,即表示物件A不可能再被使用,可進行回收。·優點:實現簡單,垃圾物件便於辨識;判定效率高,回收沒有延遲性·缺點:>需要單獨的欄位儲存計數器,這樣的做法增加了儲存空間的開銷>每次賦值都需要更新計數器,伴隨著加法和減法操作,增加了時間開銷>引用計數器有一個嚴重的問題,即 "無法處理迴圈引用" 的情況。這是一條致命的缺陷,導致在java的垃圾回收器中沒有使用這類演算法。
##3、可達性分析演算法(也叫根搜尋演算法、追蹤性垃圾收集)·相對於引用計數演算法而言,可達性分析演算法不僅同樣具備實現簡單和執行高效等特點,更重要的是該演算法可以有效地解決在引用計數演算法中迴圈引用的問題,防止記憶體洩漏的發生。·可達性分析被java c#選擇。這種型別的垃圾收集也叫做"追蹤性垃圾收集"·所謂GC Roots跟集合就是一組必須活躍的引用·基本思路:>可達性分析演算法是以根物件集合(GC Roots)為起始點,按照從上至下的方式搜尋被根物件集合所連線的目標物件是否可達。>使用可達性分析演算法後,記憶體中的存活物件都會被根物件集合直接或間接連線著,搜尋鎖走過的路徑被稱為引用鏈(Refrence Chain)>如果目標物件沒有被任何引用鏈相連,則是不可達的,就意味著該物件已經死亡,可以標記為垃圾物件>在可達性分析演算法中,只有能夠被根物件集合直接或間接連線的物件才是存活物件
·在java中,GC Roots包括以下幾類元素:>虛擬機器棧中的引用物件 eg:各個執行緒被呼叫的方法中使用到的引數、區域性變數>本地方法棧內JNI引用的物件>方法區中類靜態屬性引用的物件 eg:Java類的引用型別靜態變數>方法區中常量引用的物件 eg:字串常量池裡的引用>所有被同步鎖synchronized持有的物件>Java虛擬機器內部的引用 eg:基本資料型別物件的class物件,一些常駐的異常物件,系統類載入器>反映java虛擬機器內部情況的JMXBean、JVMTI中註冊的回撥、原生代碼快取等缺點:·如果使用可達性分析演算法判斷記憶體是否回收,需要分析工作必須在一個能保障一致性的快照中進行。這點不滿足的話分析結果的準確性就無法保證,這也是GC時必須"STW(stop the world)"的一個重要原因。
2.2、物件的finalization機制·Java語言提供了物件終止(finalization)機制來允許開發人員提供物件被銷燬前的自定義處理邏輯·當垃圾回收器發現沒有引用指向一個物件,即:垃圾回收此物件前,總會先呼叫這個物件的finalize()方法·finalize()方法允許在子類中被重寫,用於在物件被回收時進行資源釋放,通常在這個方法中進行一些資源釋放和清理工作,比如關閉檔案、套接字和資料庫連線等·永遠不要主動的呼叫某個物件的finalize()方法,應交給垃圾回收機制呼叫。原因如下:>在finalize()是可能會導致物件復活>finalize()方法的執行時間是沒有保障的,它完全由GC執行緒決定,極端情況下,若不發生GC,則finalize()方法沒有執行幾回>一個糟糕的finalize()會嚴重影響GC的效能·由於finalize()方法的存在,虛擬機器中的物件一般處於三種可能的狀態:如果從根節點都無法訪問到某個物件,說明物件已經不再使用了。一般來說此物件需要被回收。但事實上,也並非是非死不可的,這時候它們暫時處於緩刑階段。一個無法觸及的物件可能在某一個條件下復活自己,如果這樣,那麼對它的回收就是不合理的,為此,定義虛擬機器中的物件可能的三種狀態如下:>可觸及的:從根節點開始,可以到達這個物件>可復活的:物件的所有引用都被釋放,但是物件有可能在finalize()中復活>不可觸及的:物件的finalize()被呼叫,並且沒有復活,那麼就會進入不可觸及狀態。不可觸及的物件不可能被複活,因為finalize()只能被呼叫一次,類似servlet的destory()。以上三種狀態中,是由於finalize()方法的存在,進行的區分。只有在物件不可觸及時才可以被回收。##判定一個物件ObjA是否可回收的具體過程:·如果物件ObjA到GC Roots沒有引用鏈,則進行第一次標記·進行篩選,判斷次物件是否有必要執行finalize()方法:>如果obja沒有重寫finalize()方法,或者finalize()方法已經被虛擬機器呼叫過,則虛擬機器視為沒有必要執行,obja被判定為不可觸及的>如果物件obja重寫了finalize()方法,且還未執行過,那麼obja會被插入到F-Queue佇列中沒有一個虛擬機器自動建立的、低優先順序的finalizer執行緒觸發器finalize()方法執行>finalize()方法時物件逃脫死亡的最後機會,稍後GC會對F-Queue佇列中的物件進行第二次標記。如果obja在finalize()方法中與引用鏈上的任何一個物件建立了聯絡,那麼在第二次標記時,obja會被溢位即將回收集合。之後,物件會再次出現沒有引用存在的情況。這個情況下finalize方法不會被再次呼叫,物件會直接程式設計不可觸及的狀態,也就是說,一個物件的finalize方法只會被呼叫一次
2.3、MAT與JProfiler的GC Roots溯源MAT下載地址 JProfiler下載地址
MAT(memory analyzer)是一款java堆記憶體分析器,用於查詢記憶體洩漏以及檢視記憶體消耗情況[MAT是eclipse開發的,免費效能分析工具]##獲取dump檔案>方式一:命令列使用jmap>方式二:使用JVisualVM生成JVisualVM -> 監視 -> 右側堆dump -> 左側樹右擊儲存##MAT開啟dump##JProfiler也可以在idea中安裝外掛使用
2、按照工作模式分·併發式的垃圾回收器 和 獨佔式的垃圾回收器·併發式垃圾回收器與應用程式執行緒交替工作,以儘可能減少應用程式的停頓時間·獨佔式垃圾回收器一旦執行,就停止應用程式中的所有使用者執行緒,直到垃圾回收過程完全結束
3、按照碎片處理方式分·壓縮式垃圾回收器 和 非壓縮式垃圾回收器·壓縮式垃圾回收器會在回收完成後,對存活物件進行壓縮整理,消除回收後的碎片·非壓縮式的垃圾回收器不進行這不操作(需額外維護空閒列表)4、按工作記憶體區分·新生代垃圾回收器 和 老年代垃圾回收器##二、評估GC的效能指標·吞吐量:執行使用者程式碼的時間佔總執行時間的比例(主要)>執行總時間 = 程式的執行時間 + 記憶體回收的時間·暫停時間:執行垃圾收集時,程式的工作執行緒被暫停的時間(主要)·記憶體佔用:Java堆區所佔的記憶體大小>記憶體小GC頻繁,記憶體大GC的暫停時間會增大·垃圾收集開銷:吞吐量的補數,垃圾收集所用時間與總執行時間的比例·收集頻率:相對於應用程式的執行,收集操作發生的頻率·快速:一個物件從誕生到被回收所經歷的時間>高吞吐量 和 低暫停時間 是相互矛盾的>如果選擇吞吐量優先,那麼必然需要降低記憶體回收的執行頻率,但這樣會導致GC需要更長的暫停時間來執行記憶體回收>如果選擇低延遲優先的原則,那麼為了降低每次執行記憶體回收時的暫停時間,只能頻繁地執行記憶體回收,但這又引起了新生代記憶體的所見和導致程式吞吐量的下降現在標準:在最大吞吐量優先的情況下,降低停頓時間
4.2、垃圾收集器的發展 和 經典的垃圾收集器垃圾回收:Garbage Collection垃圾回收器:Garbage Collector##一、垃圾回收器發展歷史·JDK1.3: 序列方式Serial GC,它是第一款GC,ParNew垃圾收集器是Serial收集器的多執行緒版本·JDK1.4: Parallel GC 和 Concurrent Mark Sweep GC (CMS)·JDK6: Parallel GC成為HotSpot預設GC·JDK7: G1可用·JDK9:G1成為預設的垃圾收集器,以替代CMS·JDK11: 引入Epsilon和ZGC·JDK12: 引入Shenadoah GC·JDK13: 增強ZGC·JDK14: 刪除CMS,擴充套件ZGC##二、7款經典的垃圾回收器·序列回收器: Serial GC 和 Serial Old·並行回收器: ParNew 和 Parallel Scavenge 和 Parallel Old·併發回收器: CMS 和 G1##三、7款經典收集器 與 垃圾分代之間的關係·新生代收集器:Serial 和 ParNew 和 Parallel Scavenge·老年代收集器:Serial Old 和 Prallel Old 和 CMS·整堆收集器:G1
·為什麼要有這麼多垃圾收集器,因為java的使用場景不同,如移動端,伺服器等。針對不用的場景,提供不同的垃圾收集器,提高垃圾收集的效能
4.3、如何檢視預設的垃圾收集器
·-XX:+PrintCommandLineFlags 檢視命令列相關引數(包含使用的垃圾收集器)·使用命令列指令:jinfo -flag 相關垃圾回收器引數 程序ID
4.4、Serial回收器:序列回收【Serial回收新生代 Serial Old回收老年代】·Serial收集器作為HotSpot中client模式下的預設新生代垃圾收集器·Serial收集器採用"複製演算法"、"序列回收"和"STW機制"的方式執行記憶體回收·Serial Old收集器同樣採用了"序列回收"和"STW機制",只不過記憶體回收演算法使用的是 "標記-壓縮演算法">client模式下 serial old是預設的老年代回收器>server模式下 ①、與新生代的Parallel scavenge配合使用 ②、作為CMS收集器的後備垃圾收集方案·Serial收集器是一個單執行緒的收集器,但它的單執行緒的意義並不僅僅說明它只會使用一個CPU或一條收集執行緒去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其它所有工作執行緒,直到它收集結束(STOP THE WORLD)##優勢:·簡單而高效(與其它收集器單執行緒比),對於限定單個CPU的環境來說,Serial收集器由於沒有執行緒互動的開銷,專心做垃圾收集自然可以獲得最高的單執行緒收集效率>執行在client模式下的虛擬機器是個不錯的選擇·在使用者的桌面應用場景中,可用記憶體一般不大(幾十兆至一兩百兆),可以在較短時間內完成垃圾回收,只要不頻繁發生使用序列回收器是可以接受的。##配置:·在HotSpot虛擬機器中,使用-XX+UseSerialGC引數可以指定新生代和老年代都使用序列收集器>等價於 新生代使用Serial GC,老年代使用Serial Old GC##總結現在已經不用序列的垃圾回收器拉,而且在限定單核CPU才可以用,現在都不是單核的了對於互動較強的應用而言,這種垃圾收集器是不能接受的。一般在java web應用程式中是不會採用序列垃圾收集器的
4.5、ParNew回收器:並行回收【JDK9中已被移除】【ParNew回收新生代】·ParNew收集器是Serial收集器的多執行緒版本>par是Parallel的縮寫,New:表示新生代·ParNew收集器除了採用並行回收的方式執行記憶體回收外,兩款垃圾收集器之間幾乎沒有任何區別。ParNew收集器在新生代中同樣採用"複製演算法"、"STW"機制·ParNew是很多JVM執行在server模式下新生代的預設垃圾收集器>對於新生代,回收次數頻繁,使用並行方式高效>對於來年代,回收次數少,使用序列方式節省資源(CPU並行需要切換執行緒,序列可以省去切換執行緒的資源)##由於ParNew收集器是基於並行回收,那麼是否可以判定parNew收集器的回收效率在任何場景下都會比serial收集器效率更高?>ParNew收集器執行在多CPU的環境下,由於可以充分利用多CPU、多核心等屋裡硬體資源優勢,可以更快地完成垃圾收集,提升程式吞吐量>但是在單個CPU的環境下,ParNew收集器不必Serial收集器更高效。雖然serial收集器是基於序列回收,但是由於CPU不需要頻繁地做任務切換,因此可以有效避免多執行緒互動過程中產生的一些額外開銷·除了serial外,目前只有parNew GC能與CMS收集器配合工作##配置:·-XX:+UserParNewGC手動指定ParNew收集器執行記憶體回收任務。它表示新生代使用並行收集器,不影響老年代·-XX:ParallelGCThreads限制執行緒數量,預設開啟和CPU資料相同的執行緒數·-XX:UseConcMarkSweepGC設定老年代使用CMS回收器
4.6、Parallel回收器:吞吐量優先【JDK8預設回收器】【Parallel回收新生代 Parallel Old回收老年代】·Parallel是Parallel Scavenge收集器的簡寫·Parallel收集器同樣採用"複製演算法"、"並行回收" 和 "STW"機制##有了ParNew收集器,Parallel收集器的出現是否多此一舉?>和ParNew收集器不同,Parallel收集器的目標則是達到一個可控制的吞吐量,它被稱為吞吐量優先的垃圾收集器>自適應調節策略也是Parallel和ParNew一個重要區別[可動態調整記憶體區域分配情況,新生代大小,Eden和Survivor比例,晉升老年代所需年齡等,預設開啟]·高吞吐量可以高效低利用CPU時間,儘快完成程式的運算任務,主要適合在後臺運算而不需要太多互動的任務。因此常見在伺服器環境中使用。例如,哪些執行批次處理、訂單處理、工資支付、科學計算的應用程式·Parallel Old收集老年代垃圾,用來替換來年代的Serial Old收集器·Parallel Old收集器採用了"標記-壓縮演算法",但同樣也是基於"並行回收"和"STW"機制·在吞吐量優先的應用場景中,Parallel收集器和Parallel Old收集器的組合,在Server模式下的記憶體回收效能很不錯·JDK8中,預設是此垃圾回收器##引數配置:·-XX:+UseParallelGC 手動指定新生代使用Parallel並行收集器執行記憶體回收任務·-XX:+UseParallelOldGc 手動指定老年代的並行回收收集器>上面兩個引數,預設開啟一個,另一個也會被開啟(互相啟用)·-XX:ParallelGCThreads 設定新生代並行收集器的執行緒數,最好與CPU數量相等。>預設CPU數量小於8個,ParallelGCThreads的值等於CPU數量>預設CPU數量大於8個時u,ParallelGCThreads的值等於3+[5*CPUCount]/8·-XX:MaxGCPauseMillis設定垃圾收集器最大停頓時間(即STW時間),單位是ms>為儘可能地把停頓時間控制在MaxGcPasuseMillis以內,收集器在工作時會調整java堆大小或者一些其它引數>對於使用者來講,停頓時間越短體驗越好。但是在伺服器端,我們注重高併發,整體吞吐量,所以伺服器端適合Parallel,進行控制>該引數使用需謹慎·-XX:GCTimeRatio垃圾收集時間棧總時間比例[1/(N+1)]用於很小兔兔量大小>取值範圍(0,100),預設值99,也就是垃圾回收時間不超過1%>與前一個-XX:MaxGCPauseMillis引數有一定矛盾性,暫停時間越長,Radio引數就越容易超過設定的比例·-XX:+UseAdaptiveSizePolicy設定parallel scavenge收集器具有自適應調節策略【預設開啟】>在這種模式下,新生代的大小、Eden和Surivivor的比例、晉升老年代的物件年齡等引數會被自動調整,以達到堆大小、吞吐量和停頓時間之間的平衡點>在手動調整比較困難的場合,可以直接使用這種自適應的方式,僅指定虛擬機器的最大堆、目標的吞吐量和停段時間,讓虛擬機器自己完成調優工作
4.7、CMS回收器:低延遲【回收老年代】【JDK14刪除CMS】·Concurrent-Mark-Sweep收集器,簡稱CMS(標記-清除演算法)·併發收集器,第一次實現了讓垃圾收集執行緒與使用者執行緒同時工作·CMS收集器關注點是儘可能縮短STW時間,STW時間越短就越適合與使用者互動的程式>目前很大一部分java應用程式在網際網路站或B/S系統的服務端上,這類應用尤其重視服務的響應速度,希望系統停頓時間最短,已給使用者帶來較好的體驗,CMS收集器就是非常符合這類應用的要求·CMS垃圾手機演算法採用"標記-清除"演算法,並且也會STW·CMS只能和Serial和ParNew一起配合工作,不能和Parallel配合使用·在G1之前,CMS使用非常廣泛##工作過程·CMS工作的蒸鍋過程分為4個主要階段,即初始標記階段、併發標記階段、重新標記階段和併發清除階段>初始標記initial-mark:在這個階段,程式中所有的工作執行緒都會因為STW機制而出現短暫的暫停,這個階段的主要任務僅僅是標記處GC Roots能直接關聯到的物件。一旦標記完成之後就會恢復之前被暫停的所有應用執行緒。由於直接關聯物件比較小,所以這裡的速度非常快>併發標記concurrent-mark:從GC Roots的直接關聯物件開始遍歷整個物件圖的過程,這個過程耗時較長但是不需要停頓使用者執行緒,可以與垃圾收集執行緒一起併發執行>重新標記remark:由於在併發標記階段,程式的工作執行緒回合垃圾收集執行緒同時執行或者交叉執行,因此為了修正併發標記期間,因使用者程式繼續運作而導致標記產生變動的那一部分物件的標記記錄,這個階段的停頓時間通常會比初始標記階段稍微長一些,單頁遠比並發標記階段的時間短>併發清除concurrent-sweep:此階段清理刪除掉標記段判斷的已經死亡的物件,釋放記憶體空間,由於不需要移動存活物件,所以這個階段也是可以與使用者執行緒同時併發的##CMS的特點與弊端:·儘管CMS收集器採用併發揮收,但是在其初始化標記和重新標記兩個階段仍需執行STW,不過暫停時間不會太長,因此可以說明目前所有的垃圾收集器都做不到完全避免STW,只是儘可能地縮短暫停時間·由於耗費時間和併發標記與併發清除階段都不需要暫停工作,所以整體的回收時低停頓的·另外由於在垃圾收集階段使用者執行緒沒有中斷,所以CMS回收過程中,還應該確保應用程式使用者執行緒有足夠的記憶體可用。因此CMS收集器不能像其它收集器那樣等到老年代幾乎完全被填滿了再進行收集,而是當堆記憶體使用率達到某一閾值是,便開始進行回收,以確保應用在CMS工作過程中依然有足夠的空間支援應用程式執行。要是CMS執行期間預留的記憶體無法滿足程式需要,就會出現一次Concurrent Mode Failure,這是虛擬機器將啟動後背元:臨時啟用serial old收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了·CMS收集器採用"標記-清除"算反,這意味著每次執行完記憶體回收後,由於被執行記憶體回收的無用物件所佔用的記憶體空間極有可能不是連續的一些記憶體塊,不可避免地將會產生一些記憶體碎片。那麼CMS在位新物件分配記憶體空間時,將無法使用指標碰撞技術,而只能夠選擇空閒列表執行記憶體分配(維護一個空閒列表)##Mark-Sweep既然會產生記憶體碎片,為什麼CMS不採用Mark-Compact呢?·CMS是併發執行的,如果要壓縮做碎片整理,就需要停掉所有使用者執行緒,和CMS的初衷不匹配##CMS優缺點:·優點>併發收集>低延遲(STW時間非常短)·缺點>會產生記憶體碎片>CMD收集器堆CPU資源非常敏感在併發階段,它雖然不會導致使用者執行緒停頓,但是會因為佔用了一部分執行緒而導致應用程式變慢,總吞吐量降低>CMS收集器無法處理浮動垃圾可能出現Concurrent Mode Failure失敗而導致另一次Full GC的產生。在併發標記階段由於程式的工作執行緒和垃圾收集執行緒是同時執行或者交叉執行的,那麼在併發標記階段如果產生新的垃圾物件,CMS將無法對這些物件進行標記,最終導致這些新產生的垃圾物件沒有被及時回收,從而只能在下一次執行GC時釋放這些之前未被回收的記憶體空間(浮動垃圾:在併發標記的過程中,其它使用者執行緒產生的新垃圾即浮動垃圾)##引數設定·-XX:+UseConcMarkSweepGC 手動指定使用CMS收集器執行垃圾回收任務>開啟該引數後,會自動將-XX:+UseParNewGC開啟,即ParNew(新)+CMS(老)+Serial Old(老)組合·-XX:CMSInitiatingOccupanyFraction 設定堆記憶體使用率的閾值,一旦達到該閾值,變開始進行回收·-XX:+UseCMSCompactAtFullCollection 用於指定在執行完Full GC後堆記憶體空間進行壓縮整理,以此避免記憶體碎片的產生,不過由於記憶體壓縮整理過程無法併發執行,所帶來的的問題就是停頓時間變得更長了·-XX:CMSFullGCsBeforeCompaction 設定在執行多少次Full GC後堆記憶體進行壓縮整理·-XX:ParallelCMSThreads 設定CMS的執行緒數量>CMS預設啟動的執行緒數是(ParallelGCThreads+3)/4,ParallelGCThreads是新生代並行收集器的執行緒數。當CPU資源比較緊張時,收到CMS收集器執行緒的影響,應用程式的效能在垃圾回首階段可能會非常糟糕
4.8、G1回收器:區域化分代式【JDK9以後預設使用的】【JDK8可用,但還不是預設,需-XX:UseG1GC】·G1:garbage first·G1是為了適應不斷擴大的記憶體和不斷增加的處理器數量,進一步降低暫停時間,同時兼顧良好的吞吐量而產生的·G1設定的目標是在延遲可控的情況下獲得儘可能高的吞吐量·G1是一款面向伺服器端應用的垃圾收集器,主要針對配備多核CPU及大容量記憶體的機器,以極高機率滿足GC停頓時間的同時,還兼具高吞吐量的效能特徵##一、為什麼叫做Garbage First?·G1是一個"並行回收器",它把堆記憶體分割為很多不相關的區域region(物理上不連續的),使用不同的region來表示Eden、倖存者0區、倖存者1區、老年代等·G1 GC有計劃地避免在整個java堆中進行全區域的垃圾收集。G1跟蹤各個region裡面的垃圾堆積的價值大小(回收所獲得的的空間大小以及回收需要時間的經驗值),在後臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的region·由於這種方式的側重點在於回收垃圾最大量的區間region,所以我們給G1一個名字:垃圾優先Garbage First##二、G1回收器的優勢(與其它GC收集器相比,G1採用了全新的分割槽演算法)1·並行與併發兼具>並行性:G1在回收期間,可以有多個GC執行緒同時工作,有效利用多核計算能力。此時使用者執行緒STW>併發性:G1擁有與應用程式交替執行的能力,部分工作可以和應用程式同時執行,因此,一般來說,不會再整個回首階段發生完全阻塞應用的情況2·分代收集>G1屬於分代性垃圾收集器,它會區分新生代和來年代,新生代依然有Eden區和Surivivor區,但從堆的結構上看,它不要求整個Eden區、新生代或者老年代都是連續的,也不再堅持固定大小和固定數量(這段時間可以是Eden區,下次垃圾回收後可能是Surivivor區)>將堆空間分為若干個區域,這些區域中包含了邏輯上的新生代和老年代>和之前的各類回收器不同,它同時兼顧新生代和老年代。3·空間整合>CMS:"標記-清除"演算法、記憶體碎片、若干次GC後進行一次碎片整理>G1將記憶體劃分為一個個的region,記憶體的回收是以region作為基本單位的。region之間是複製演算法,但整體上實際可看做是"標記-壓縮"演算法,兩種演算法都可以避免記憶體碎片,這種特性有利於程式長時間執行,分配大物件時不會因為無法找到連續記憶體空間而提前觸發下一次GC。尤其是當Java堆非常大的時候,G1的優勢更加明顯4·可預測的停頓時間模型(即:軟實時soft real-time)這是G1相對於CMS的另一大優勢,G1除了追求地停頓外,還建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為M毫秒的時間段內,消耗在垃圾收集上的時間不得超過N毫秒>由於分割槽的原因,G1可以只選取部分割槽域進行記憶體回收,這樣縮小了回收的範圍,因此對於全域性停頓的情況的發生也能得到較好的控制>G1跟蹤各個region裡面的垃圾堆積的價值大小,在後臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的region,保證了G1收集器在有限的時間內可以獲取儘可能高的收集效率>相較於CMS,G1未必能做到CMS在最好情況下的延時停頓,但是最差情況要好很多##三、G1的缺點相較於CMS,G1無論是為垃圾收集產生的記憶體佔用,還是程式執行時的額外執行負載都要比CMS高。從經驗上來說,在小記憶體應用上CMS的表現大機率會優於G1,而G1在大記憶體應用上則發揮其優勢,平衡點在6-8GB之間##四、引數設定·-XX:+UseG1GC 手動指定使用G1收集器執行記憶體回收任務(JDK9以後是預設的,JDK8可用但須設定)·-XX:G1HeapRegionSize 設定每個region的大小,值是2的冪,範圍是1MB~32MB之間,目標是根據最小的java堆大小劃分出約2048個區域。預設是堆記憶體的1/2000·-XX:MAXGCPauseMillis 設定期望達到的最大GC停頓時間指標,預設值是200ms·-XX:ParallelGCThread 設定STW時GC執行緒數值,最多設定為8·-XX:ConcGCThreads 設定併發標記的執行緒數,將n設定為並行垃圾回收執行緒數(ParallelGCThreads)的1/4左右·-XX:InitiatingHeapOccupancyPercent 設定觸發併發GC週期的Java堆佔用率閾值,超過此值,就觸發GC。預設值是45G1的設計原則就是簡化JVM效能調優,我們只需三步即可完成調優:1>開啟G1垃圾收集器-XX:+UseG1GC2>設定堆的最大記憶體-Xms -Xmn3>設定最大的停頓時間-XX:ParallelGCThread##五、G1回收器的使用場景·面對伺服器端,針對具有大記憶體、多處理器的機器·需要低GC延遲,具有大堆(6G或者更大時)的應用程式·下面的一些情況下,使用G1效能比CMS好>超過50%的java堆被活動資料佔用>物件分配頻率或年代提升頻率變化很大>GC停頓時間過長(長於0.5至1秒)##六、Region的使用介紹·使用G1收集器時,它將整個Java堆劃分稱謂約2048個大小相同的獨立region塊,每個region塊大小根據堆空間的實際大小而定,整日被控制在1MB到32MB之間,且為2的N次冪,可以透過-XX:G1HeapRegionSize設定。【所有region的大小相同,且在JVM生命週期內不會改變】·雖然還保留有新生代和老年代的概念,但新生代和來年代不再是物理隔離的了,它們都是一部分region(不需要連續)的集合。透過region的動態分配方式實現邏輯上的連續·一個region可能屬於Eden,survivor或者old記憶體區域。但是一個region只能屬於一個角色·G1垃圾收集器還增加了一種新的記憶體區域Humongous記憶體區域,主要用於儲存大物件,如果超過0.5個region,就放到H設定H區的原因:對於堆中的大物件,預設直接會被分配到老年代,但是如果它是一個短期存在的大物件,就會對垃圾收集器造成負面影響。為了解決這個問題,G1劃分了一個Humongous區,它用來專門存放大物件。如果一個H區裝不下一個大物件,那麼G1會尋找連續的H區來儲存,為了能找到連續的H區,有時候不得不啟動Full GC,G1的大多數行為都把H區作為老年代的一部分來看待##七、G1回收器垃圾回收過程G1的垃圾回收主要包括如下三個環節:>新生代GC(Young GC)>老年代併發標記過程(Concurrent Marking)>混合回收(Mixed GC)(如果需要,單執行緒、獨佔式、高強度的Full GC還是繼續存在的,它針對GC的平菇失敗提供了一種失敗保護機制,即強力回收)·應用程式分配記憶體,當新生代的Eden區用盡時開始新生代回收過程:G1的新生代收集階段是一個並行的獨佔式收集器。在新生代回收期,G1 GC暫停所有應用程式執行緒(STW),啟動多執行緒執行新生代回收,然後從新生代區間移動存活物件到Survivor區間或者老年區間,也有可能是兩個區間都會涉及·當堆記憶體使用叨叨一定值時(預設45%),開始老年代併發標記過程·標記完成馬上開始混合回收過程。對於一個混合回收器,G1 GC從老年區間移動存活物件到空閒區間,這些空閒區間也就成為了老年代的一部分。和新生代不同,老年代的G1回收器和其他GC不同,G1的老年代回收器不需要整個老年代被回收,一次只需掃描/回收一小部分老年代的region就可以了。同時,這個老年代region是和新生代一起被回收的eg:一個Web伺服器,java程序最大堆記憶體為4G,每分鐘響應1500個請求,每45秒中會新分配大約2G的記憶體。G1會每45秒中進行一次新生代回收,每31個小時整個堆的使用率會達到45%,會開始老年代併發標記過程,標記完成後開始四到五次的混合回收##八、記憶集與寫屏障Remembered Set: R Set#問題:·一個物件被不同區域引用的問題·一個region不可能是孤立的,一個region中的物件可能被其他任意region中的物件引用,判斷物件存活是,是否需要掃描整個java堆才能保證準確?·在其他的分代收集器,也存在這樣的問題(G1更突出)·回收新生代也不得不同時掃描老年代?·這樣的話會降低Minor GC的效率#解決·無論G1還是其他分代收集器,JVM都是使用 Remembered Set(記憶集)來避免全域性掃描·每個region都有一個對應的remembered set·每次reference型別資料寫操作是,都會長生一個write barrier(寫屏障)暫時中斷操作·然後檢查將要寫入的引用指向的物件是否和該refrences型別資料在不同的region·如果不用,透過cardtable把相關引用資訊記錄到引用指向物件的所在region對應的remembered set中·當進行垃圾收集時,在GC根節點的列舉範圍加入remembered set,就可以保證不進行全域性掃描,也不會有遺漏##九、G1垃圾回收過程1、新生代GC:JVM啟動時,G1先準備好Eden區,程式在執行過程中不斷建立物件到Eden區,當Eden空間耗盡時,G1會啟動一次年輕代垃圾回收過程年輕代垃圾回收只會回收Eden區和Surivivor區YGC時,首先G1停止應用程式的執行STW,G1建立回收機(Collection Set),回收集是指需要被回收的記憶體分段的集合,年輕代回收過程的回收集包括年輕代Eden區和Survivor區所有的記憶體分段。(Eden區滿了會觸發YGC,但Survivor區滿了不會觸發YGC)過程:>第一階段:掃描根根是指static變數指向的物件,正在執行的方法呼叫鏈條上的區域性變數等,根引用連用rset記錄的外部引用作為掃描存活物件的入口>第二階段:更新rset處理dirty card queue中的card,更新rset,此階段完成後,rset可以準確的反映老年代對所在的記憶體分段中的物件引用>第三階段:處理rset識別被來年代物件指向的Eden中的物件,這些被指向的Eden中的物件被認為是存活的物件>第四階段:複製物件此階段,物件樹被遍歷,Eden區記憶體段中存活的物件會被複制到survivor區中空的記憶體分段,survivor區中記憶體段中存活的物件如果年齡未達到閾值,年齡會加1,大道與之會被複制到old區中空的記憶體分段,如果survivor空間不夠,Eden空間的部分資料直接晉升到old空間>第五階段:處理引用處理soft、weak、phantom、final、JNI Weak等引用,最終Eden空間的資料為空,GC停止工作,而目標記憶體中的物件都是連續儲存的,沒有碎片,所以賦值過程可以達到記憶體整理的效果,減少碎片。2、併發標記過程:>初始標記階段:標記從根節點直接可達的物件。這個階段是STW的沒並且會觸發一次年輕代GC>根區域掃描G1掃描Survivor區直接可達的老年代區域物件,並標記被引用的物件,這一過程必須在YGC之前完成>併發標記在整個堆中進行併發標記,此過程可能被YGC中斷,在併發標記階段,若發現物件中的所有物件都是垃圾,那這個區域會被立即回收,同時併發標記過程中,會計算每個區域的物件活性>再次標記由於應用程式持續進行,需要修正上一次的標記結果,是STW的,G1中採用了比CMS更快的快照演算法>獨佔清理計算各個區域的存活物件和GC回收比例,並進行排序,識別可以混合回收的區域,為下階段做鋪墊,是STW的(這個階段並不會實際上去做垃圾的收集)>併發清理階段識別並清理完全空閒的區域3、混合回收當越來越多的物件晉升到old region時,為了避免堆記憶體被耗盡,虛擬機器會觸發一個混合的垃圾收集器,即Mixed GC,該演算法並不是一個Old GC,除了回收整個Young Region還會回收一部分的Old Region,這裡需要注意:是一部分老年代,而不是全部老年代。可以選擇那些oldregion進行收集,從而可以對垃圾回收的耗時時間進行控制。也需要注意的是Mixed GC並不是Full GC>併發標記結束以後,老年代中百分百為垃圾的記憶體分段被回收了,部分為垃圾的記憶體分段被計算了出來。預設情況下,這些老年代的記憶體分段會分8次被回收>混合回收的收集器包括八分之一的來年代記憶體分段,Eden區記憶體分段,Survivor區記憶體分段。混合回收的演算法和年輕代回收的演算法完全一樣,只是回收集多了老年代的記憶體分段>由於來年代中的記憶體分段預設分8次回收,G1會優先回收垃圾多的記憶體段,垃圾站村分段比例越高,越先被回收>混合回收並不一定要進行8次,有一個閾值-XX:G1HeapWastePercent,預設為10%,意思是允許整個堆記憶體中有10%的空間被浪費,意味著如果發現可以回收的垃圾佔堆記憶體的比例低於10%,則不再進行混合回收。因為GC會花費很多的時間但是會受到的記憶體卻很少4、Full GCG1的初衷是避免Full GC的出現,但是如果上述方式不能正常工作,G1會STW,使用單執行緒的記憶體回收演算法進行垃圾回收,效能會非常差,應用程式停頓時間會很長
4.9、垃圾回收器總結·最小化地使用記憶體和並行開銷:選擇Serial GC + Serial Old·最大化應用程式的吞吐量:選擇Parallel GC + Parallel Old·最小化GC的中斷或停頓時間:選擇CMS GC + ParNew + Serial Old·JDK9 廢棄了CMS JDK14刪除了CMS(新生代大部分是複製演算法 老年代大部分是標記-整理、標記-清除演算法)
5.0、GC日誌分析
##一、日誌引數-XX:+PrintGC 列印GC日誌-XX:+PrintGCDetails 列印日誌詳情-XX:+PrintGCTimeStamps 列印GC的時間戳(以基準時間形式)-XX:+PrintGCDateStamps 列印GC的時間戳(以日期的形式)-XX:+PrintHeapAtGC 在進行GC的前後打印出堆的資訊-Xloggc:./logs/gc.log 日誌檔案的輸出路徑
##二、GC日誌分析GC 表示只在"新生代"上進行Full GC 包括"新生代""老年代""元空間" (會發生STW)PSYoungen : Parallel Scavenge收集器新生代的名稱DefNew : 使用了Serial收集器新生代的名稱 Default New GenerationParNew :ParNew收集器在新生代的名稱 Parallel New Generationgarbage-first heap : G1收集器ParOldGen : Parallel OldAllocation Failure : GC發生的原因3745K->1127K(58880K) : 堆在GC前的大小 和 GC後的大小 和 本身總的大小0.0083899 secs : GC持續時間##三、常見的日誌分析工具先使用-Xloggc:./logs/gc.log 日誌檔案的輸出路徑在用工具:GCViewer、GCEasy、GCHisto、GCLogViewer、Hpjmeter、garbagecat等
5.1、新時期的垃圾回收器·Epsilon : A No-Op Garbage Collector(無操作)[只做記憶體分配,不做垃圾回收(執行完直接退出程式的場景)]·Shenandoah GC : 低停頓,但吞吐量下降了 (RedHat開發)·ZGC : A Scalable Low-Latency Garbage Collector(可擴充套件、低延遲[停頓])[基於Region的記憶體佈局、可併發的標記壓縮演算法、低延遲為目標][併發標記-併發與被重分配-併發重分配-併發重對映]