天天看點

JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)

JVM系列之:記憶體與垃圾回收篇(三)

##本篇内容概述:
1、執行引擎
2、StringTable
3、垃圾回收
           

一、執行引擎

##一、執行引擎概述
如果想讓一個java程式運作起來,執行引擎的任務就是将位元組碼指令解釋/編譯為對應平台上的本地機器指令才可以。
簡單來說,JVM中的執行引擎充當了将改機語言翻譯為機器語言的譯者。

##二、執行引擎的工作過程
1)執行引擎在執行的過程中究竟需要執行什麼樣的位元組碼指令完全依賴于PC寄存器

2)每當執行完一項操作後,PC寄存器就會更新下一條需要被執行的指令位址

3)當方法在執行的過程中,執行引擎有可能會通過存儲在局部變量表中的
對象引用準确定位到存儲在Java堆區中的對象執行個體資訊,以及通過對象頭
中的中繼資料指針定位到目标對象的類型資訊。
           
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)
##三、什麼是解釋器,什麼是JIT編譯器?
解釋器:當java虛拟機啟動時會根據預定義的規範對位元組碼采用逐行解釋
的方式執行,将每條位元組碼檔案中的内容翻譯為對應平台的本地機器指令
執行。(解釋執行)

JIT編譯器:就是虛拟機将源碼直接編譯成和本地機器平台相關的語言。(先翻譯好,放在方法區等待執行)


##四、解釋器
JVM設計者初衷僅僅隻是單純地為了滿足java程式實作跨平台的特性,因
此避免了采用靜态編譯的方式直接生成本地機器指令,進而誕生了實作解
釋器在運作時采用逐行解釋位元組碼執行程式的想法。

解釋器真正意義上所承擔的角色是一個運作時翻譯者,将位元組碼檔案中的
内容翻譯為對應平台的本地機器指令執行。

當一條位元組碼指令被解釋執行完成後,接着在根據PC寄存器中記錄的下一
條需要被執行的位元組碼指令執行解釋操作

##五、JIT編譯器
基于解釋器執行已經淪為低效的代名詞,為了解決這個問題,JVM平台支援一種叫做即時編譯器的技術。即使編譯的目的是避免函數被解釋執行,而是将整個函數體編譯成為機器碼,每次函數執行時,隻執行編譯後的機器碼即可,這種方式可以使執行效率大幅提升。


##六、HotSpot為何解釋器和JIT編譯器共存?
首先,在程式啟動後,解釋器可以馬上發揮作用,省去編譯的時間,立即執行。
編譯器要想發揮作用,把代碼編譯成本地代碼,需要一定的執行時間。但編譯為本地代碼後,執行效率高。

是以,當JAVA虛拟機啟動時,解釋器可以首先發揮作用,而不必等待即時
編譯器全部編譯完成後再執行,這樣可以省去許多不必要的編譯時間。随
着時間的推移,編譯器發揮作用,把越來越多的代碼編譯成本地代碼,獲
得更高的執行效率。

同時,解釋執行在編譯器進行激進優化不成立的時候,作為編譯器的逃生門。



##七、熱點代碼及探測方式
代碼是否需要啟動JIT編譯器将位元組碼直接便以為對應平台的本地機器指令則需要根據代碼被調用的頻率而定。哪些需要被編譯為本地代碼的位元組碼被稱為熱點代碼。

目前HotSpot VM所采用的的熱點探測方式是基于計數器的熱點探測:
方法調用計數器,client模式下是1500次,server模式下是
10000次才會觸發JIT編譯(不是按調用絕對次數統計的,調用次數
有熱度衰減)。
回邊計數器,統計一個方法中循環體代碼執行的次數,在位元組碼中遇到控制流向後跳轉的指令成為回邊。

##八、設定HotSpot模式
-Xint完全采用解釋器模式執行程式

-Xcomp完全采用JIT編譯器模式執行程式。如果即時編譯器出現問題,解釋器會介入執行。

-Xmixed采用解釋器+即時編譯器的混合模式共同執行程式。



           
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)

二、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的垃圾回收器中沒有使用這類算法。

           
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)
##3、可達性分析算法(也叫根搜尋算法、追蹤性垃圾收集)

·相對于引用計數算法而言,可達性分析算法不僅同樣具備實作簡單和執行高效等特點,更重要的是該算法可以有效地解決在引用計數算法中循環引用的問題,防止記憶體洩漏的發生。

·可達性分析被java c#選擇。這種類型的垃圾收集也叫做"追蹤性垃圾收集"

·所謂GC Roots跟集合就是一組必須活躍的引用

·基本思路:
>可達性分析算法是以根對象集合(GC Roots)為起始點,按照從上至下的方式搜尋被根對象集合所連接配接的目标對象是否可達。

>使用可達性分析算法後,記憶體中的存活對象都會被根對象集合直接或間接連接配接着,搜尋鎖走過的路徑被稱為引用鍊(Refrence Chain)

>如果目标對象沒有被任何引用鍊相連,則是不可達的,就意味着該對象已經死亡,可以标記為垃圾對象

>在可達性分析算法中,隻有能夠被根對象集合直接或間接連接配接的對象才是存活對象
           
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)
·在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中安裝插件使用
           
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)

2.4、垃圾清除階段的算法

##1、垃圾清除

當成功區分出記憶體中存活對象和死亡對象後,GC接下來的任務就是執行垃圾回收,釋放掉無用對象所占用的記憶體
空間,以便有足夠的可用記憶體空間為新對象配置設定記憶體。

目前JVM中比較常見的三種垃圾清除算法是:
>标記-清除算法(mark-sweep)
>複制算法
>标記-壓縮算法


##2、标記-清除算法(mark-sweep)
執行過程:
·當堆中的有效記憶體空間被耗盡的時候,就會停止整個程式(STW),然後進行兩項工作,第一項是标記,第二項是清除。
>标記:collector從引用根節點開始周遊,标記所有被引用的對象。一般是在對象的header中記錄為可達對象
>清除:collector對堆記憶體從頭到尾進行現行周遊,如果發現某個對象在其Header中沒有标記為
可達對象,則将其回收(記憶體空間碎片化了),并将其位址記錄在空閑清單中以便之後使用

缺點:
>效率不高(兩次全周遊)
>在進行GC時,需要停止整個應用程式(STW),使用者體驗差
>這種方式清理出來的空閑記憶體是不連續的,産生記憶體碎片,需要維護一個空閑清單(記錄哪個位置是空閑的可以存放對象)

注意:
這裡所謂的清除并不是真的置空,而是把需要清除的對象位址儲存在空閑的位址清單裡。下次有新
對象需要加載時,判斷垃圾的位置空間是否夠,如果夠,就存放。【其實是覆寫】


##3、複制算法(copying)--->适用于新生代
核心思想:
·将活着的記憶體空間分為兩塊,每次隻使用其中一塊,在垃圾回收時将正在使用的記憶體中的存活對象
複制到未被使用的記憶體塊中,之後清除正在使用的記憶體塊中的所有對象,交換兩個記憶體的角色,最
後完成垃圾回收。【類似堆區新生代中的Eden區和survivor0區和survivor1區】

優點:
>沒有标記和清除過程,實作簡單,運作高效
>複制過去以後保證空間的連續性,不會出現記憶體碎片問題

缺點:
>此算法可用記憶體空間被砍半了
>對于G1這種分拆成為大量region的gc,複制而不是移動,意味着GC需要維護region之間對象引
用關系,不管是記憶體占用或者時間開銷也不小.(移動後 棧對堆中對象的引用位址就會發生變化需要重新設定)

應用場景:
·新生代中的對象大部分是朝生夕死的,回收的成本效益高,是以現在的商業虛拟機都是用這種收集算法回收新生代。


##4、标記-壓縮算法(mark-compact)--->适用于老年代
執行過程:
·第一階段和标記-清除算法一樣,從根節點開始标記所有被引用對象
·第二階段将所有的存活對象壓縮到記憶體的一段,按順序排放
·之後清理邊界外所有空間(整理記憶體,防止了記憶體碎片)


标記壓縮算法的最終效果等同于标記-清除算法執行完後,再進行一次記憶體碎片整理。
二者本質差異在于标記-清除算法是一種非移動式的回收算法,标記-壓縮算法是移動式的。
可以看到,标記存活對象将會被整理,按照記憶體位址一次排列,而未被标記的記憶體會被清理掉。如
此一來,當我們需要給新對象配置設定記憶體是,JVM隻需要持有一個記憶體的起始位址即可,這比維護一個
空閑清單顯然少了許多開銷。
【同時堆中 可觸及的存活對象位址的移動,需要修改引用者的引用位址】

優點:
>解決了标記-清除算法中記憶體碎片和JVM必須維護一個可用空閑清單的缺點,此時JVM隻需維護一個記憶體起始位址即可。
>消除了複制算法中記憶體減半的高額代價

缺點:
>效率上要低于複制算法
>移動對象的同時,還需要調整引用者引用的位址
>移動過程中,需要STW全程暫停使用者應用程式



##5、三種算法的對比
複制算法在效率上講是最快的,但是浪費了一半記憶體

标記-整理算法效率最低,比複制算法多了一個标記階段,比标記-清除多了個整理記憶體的階段,但卻有效減少了記憶體碎片問題。

整體上說:複制算法适用于新生代,标記-清理和标記-整理算法适用于老年代

總結:
·目前幾乎所有的GC都是采用 "分帶收集算法"  執行垃圾回收的。

·新生代:區域相對小,對象生命周期短、存活率低、揮手頻繁,這種情況下适用于複制算法
·老年代:區域較大,對象生命周期長、存活率高,回收不及新生代頻繁,一般由标記-清除或者标記-整理算法的混合實作


##6、增量收集算法
上述算法,在垃圾回收過程中,應用軟體将處于STW狀态,在STW狀态下,應用程式所有線程都會挂起,暫
停一切正常工作。等待垃圾的回收完成,嚴重影響了使用者體驗和系統的穩定性。

·基本思想
如果一次性将所有的垃圾進行處理,需要造成系統長時間的停頓,那麼就可以讓垃圾收集線程和應用程式線
程交替執行。每次,垃圾收集線程隻收集一小片區域的記憶體空間,接着切換到應用程式線程。依次反複,直
到垃圾收集完成。

總的來說,增量收集算法的基礎仍是傳統的标記-清除算法和複制算法。增量收集算法通過對線程間沖突的
妥善處理,允許垃圾收集線程以分段的方式完成标記、清理或複制工作。

缺點:
使用這種方式,由于在垃圾回收過程中,間斷性地還執行了應用程式代碼,是以能減少系統的停頓時間。
但是,因為線程切換和上下文轉換的消耗,會是的垃圾回收的總體成本上升,造成系統吞吐量的下降


##7、分區算法
一般來說,相同條件下,堆空間越大,一次GC時所需要的時間越長,有關GC産生的停頓也就越長。
為了更好滴控制GC産生的停頓時間,将一塊大的記憶體區域分割成多個小塊,根據目标的停頓時間,每次合理地回收若幹個小區間,而不是整個堆空間,進而減少一次GC所産生的的停頓。

分代算法将按照對象的生命周期長短劃分成兩個部分(新生代和老年代),分區算法将整個堆空間劃分成連續的不同小區間。

每個小區間都是獨立使用,獨立回收。這種算法的好處是可以控制一次回收多少個小區間。
           

3、垃圾回收相關概念

3.1、System.GC

·預設情況下,通過System.gc()或者Runtime.getRuntime().gc()的調用,會顯式觸發Full GC,同時對老年代和新生代進行回收,嘗試釋放被丢棄對象占用的記憶體

·然而System.gc()調用"無法保證對垃圾收集器的調用"

·JVM實作者可以通過System.gc()調用來決定JVM的GC行為。而一般情況下,垃圾回收
應該是自動進行的,無需手動觸發,否則就太過于麻煩了。在一些特殊情況下,如我們正在
編寫一個性能基準,我們可以在運作期間調用System.gc()

·System.runFinalization()強制調用使用引用的對象的finalize()方法
(垃圾回收此對象前,總會先調用這個對象的finalize()方法)
           

3.2、記憶體溢出與記憶體洩漏

##一、記憶體溢出OOM:

·記憶體溢出是引發程式崩潰的罪魁禍首之一

·由于GC一直在發展,所有一般情況下,除非應用程式占用的記憶體增長速度非常快,造成垃圾
回收已經跟不上記憶體消耗的速度,否則不太容易出現OOM的情況

·大多數情況下,GC會進行各種年齡段的垃圾回收,實作不行了就來一次獨占式的Full GC操
作,這時候會回收大量的記憶體,供應用程式繼續使用

·Javadoc中對OOM的解釋是:沒有空閑記憶體,并且垃圾收集器也無法提供更多記憶體

·沒有空閑記憶體的情況:說明JVM的堆記憶體不夠,原因有二:
>Java虛拟機的堆記憶體設定不夠
比如:可能存在記憶體洩漏問題;也很有可能就是堆的大小不合理,比如我們要處理比較可觀的
資料量,但是沒有顯示指定JVM堆大小或者指定數值偏小。我們可以通過參數-Xms\-Xmx來
調整
>代碼中建立了大量大對象,并且長時間不能被垃圾收集器收集(存在被引用)
java.lang.OutOfMemoryError:PermGen space
java.lang.OutOfMemoryError:Metaspace

·在抛出OOM之前,通常垃圾收集器會被觸發,盡其所能去清理出空間


##二、記憶體洩漏Memory Leak:
·記憶體洩漏也可以稱作 "存儲滲漏"。嚴格來說,隻有對象不會再被程式用到了,但是GC又不
能回收它們的情況,才叫記憶體洩漏。

·但實際情況很多時候一些不太好的實踐會導緻對象的生命周期變得很長甚至導緻OOM,也可
以叫做寬泛意義上的記憶體洩漏

·盡管記憶體洩漏并不會立刻引起程式崩潰,但是一旦發生記憶體洩漏,程式中的可用記憶體就會被
逐漸蠶食,直至耗盡所有記憶體,最終出現OOM異常,導緻程式崩潰。

·注意,這裡的存儲空間并不是指實體記憶體,而是指虛拟記憶體大小,這個虛拟記憶體大小取決于
磁盤交換區設定的大小

eg:
1、單例模式:單例的生命周期和應用程式是一樣長的,是以單例程式中,如果持有對象外部
對象的引用的話,那麼這個外部對象是不能被回收的,則會導緻記憶體洩漏的産生
2、一些提供close的資源未關閉導緻的記憶體洩漏
如:datasource.getConnetction()、網絡連接配接socket和IO連接配接必須手動close,否
則是不能被回收的
           

3.3、STW

·Stop The World(STW),指的是GC事件發生過程中,會産生應用程式的停頓。停頓産生時整個應用程式線程都會被暫停,沒有任何響應,這個停頓稱為STW

·被STW中斷的應用程式線程會在完成GC之後恢複.

·STW事件和曹勇哪款GC無關,所有的GC都有這個事件

·STW是JVM在背景自動發起和自動完成的。在使用者不可見的情況下,把使用者正常的工作線程全部停掉

·開發中不要使用System.gc()會導緻STW的發生

(JVM在周遊GC Roots的時候需要在一個能確定一緻性的快照中進行,是以會暫停一切使用者程式進而産生STW)
           

3.4、垃圾回收的并發與并行

##一、并發Concurrent
·在作業系統中,是指一個時間段中有幾個程式都處于已啟動運作到運作完畢之間,且這幾個
程式都在同一個處理器上運作

·并發并不是真真意義上的"同時進行",隻是CPU把一個時間段劃分成幾個時間片段,然後在
這幾個時間區間之間來回切換,由于CPU處理速度非常快,隻要時間間隔處理得當,即可讓用
戶感覺是多個應用程式同時在進行

##二、并行Parallel
·當系統有一個以上CPU時,當一個CPU執行一個程序時,另一個CPU可以執行另一個程序,兩
個程序互不搶占CPU資源,可以同時進行,我們稱之為并行

·其實決定并行的因素不是CPU的數量,而是CPU的核心數量,比如一個CPU多個核也可以并行

·适合科學計算,背景處理等弱互動場景

##三、并發與并行對比

并發,指的是多個事情,在同一時間段内同時發生
并行,指的是多個事情,在同一時間點上同時發生

并發的多個任務之間是互相搶占資源的
并行的多個任務之間是不互相搶占資源的

隻有在多CPU或者一個CPU多核的情況下,才會發生并行。
否則看似同時發生的事情,其實都是并發執行的


##四、垃圾回收中的并發與并行

·并行:指多條垃圾收集線程并行工作,但此時使用者線程仍處于等待狀态
>如ParNew、Parallel Scavenge、Parallel Old


·并發:指使用者線程與垃圾收集線程同時執行,垃圾回收線程在執行時不會停頓使用者程式的運
行(不一定是并行的,可能會交替執行)
>使用者程式在繼續運作,二垃圾收集程式線程運作于另一個CPU上
>如CMS、G1


·串行
>相較于并行的概念,單線程執行
>如果記憶體不夠,則程式暫停,啟動JVM垃圾回收器進行垃圾回收,回收完,再啟動程式的線程


           

3.5、安全點與安全區域

##一、安全點safe point
·程式執行時并非在所有地方都能停頓下來開始GC,隻有在特定的位置才能停頓下來開始GC,
這些位置稱為"安全點safepoint"

·safe point的選擇很重要,如果太少可能導緻GC等待的時間太長,如果太頻繁可能導緻運
行時的性能問題。大部分指令的執行時間都非常短暫,通常會根據"是否具有讓程式長時間執
行的特征"為标準。比如:選擇一些執行時間較長的指令作為safe point,如方法調用、循
環跳轉和異常跳轉等

·如何在GC發生時,檢查所有線程都跑到最近的安全點停頓下來?
>搶先式中斷(目前沒有虛拟機采用了)
首先中斷所有線程,如果線程不再安全點,就恢複線程,讓線程跑到安全點
>主動式中斷
設定一個中斷标志,各個線程運作到safe point的時候主動輪訓這個标志,如果中斷标志為真,則将自己進行中斷挂起

##二、安全區域safe ragion
·safepoint機制保證了程式執行時,在不太長的時間内就會遇到可進入GC的safepoint。
但是程式不執行的時候呢?例如線程處于sleep狀态或blocked狀态,這時候線程無法響應
jvm的中斷請求,走到安全點去中斷挂起,JVM也不太可能等待線程被喚醒。杜宇這種情況,
就需要安全區域來解決

·安全區域是指在一段代碼片段中,對象的引用關系不會發生變化,在這個區域中的任何位置
開始GC都是安全的。我們也可以吧safe region看作是被擴充了的safepoint

·實際執行時:
>當線程運作到safe region的代碼時,首先辨別已經進入了safe region,如果這段時間
内發生GC,JVM會忽略辨別為Safe Region狀态的線程

>當線程即将離開safe region時,會檢查JVM是否已經完成GC,如果完成了,則繼續運作,否則線程必須等待直到收到可以安全離開safe region的信号為止


           

3.6、強引用、軟引用、弱引用、虛引用、終結器引用(重)

我們希望能描述這樣一類對象:當記憶體空間還足夠時,則能保留在記憶體中;如果記憶體空間在進
行垃圾收集後還是很緊張,則可以抛棄這些對象。

java對引用的概念進行了擴充:将引用分為強引用strong reference、軟引用soft 
reference、弱引用weak reference和虛引用phantom reference四種,這四種引用
強度一次逐漸減弱。

除了強引用外,其它3中引用可以在java.lang.ref包中找到,開發人員可以在應用程式中
直接使用它們。

java.lang.ref中隻有終結器引用時包内可見的,其它均為public,可以在應用程式中直接使用

##一、概念:
>強引用:最傳統的引用定義,是指在代碼中普遍存在的引用指派,即類似Object obj = new Object()這種引用關系。
無論任何情況下,隻要強引用關系還存在,垃圾收集器就永遠不會回收掉被引用的對象
【隻要關系存在,無論記憶體是否充足都不能被回收】

>軟引用:在系統将要發生記憶體溢出之前,将會把這些對象列入回收範圍之中進行第二次回
收。如果這次回收後還沒有足夠的記憶體,才會抛出OOM
【即使關系存在,隻要記憶體不足還是要被回收】

>弱引用:被弱引用關聯的對象隻能生存到下一次垃圾收集之前,當垃圾收集器工作時,無論
記憶體空間是否足夠,都會回收掉被弱引用關聯的對象
【隻要垃圾收集,無論關系是否存在,也無論記憶體是否充足都要被回收】

>虛引用:一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引
用來獲得一個對象的執行個體。為一個對象設定虛引用關聯的唯一目的就是能在這個對象被收集器
回收時收到一個系統通知
【虛引用對垃圾收集沒影響,隻是做對象回收跟蹤的,随時可能被垃圾回收器回收】

##二、強引用strong reference:不回收
·當java中使用new操作符建立一個新對象,并将其指派給一個變量的時候,這個變量就成為指向該對象的一個強引用

·隻要強引用的對象時刻觸及的,垃圾收集器就永遠不會回收掉被引用的對象

·對于一個普通對象,如果沒有其它的引用關系,隻要超過了引用的作用域或者顯式地将響應引用指派為null,就可以當做垃圾被收集了,當然具體回收時機還是要看垃圾收集政策。

·強引用時造成java記憶體洩漏的主要元兇之一

eg:
StringBuffer s1 = new StringBuffer("yyyyy");
StringBuffer s2 = s1;


##二、軟引用soft reference:記憶體不足即回收
·軟引用是用來描述一些還有用,但非必須的對象。隻被軟引用關聯着的對象在系統将要發生記憶體溢出前,會把這些對象列進回收範圍之中進行第二次回收,如果這次回收還沒足夠的記憶體,才會抛出記憶體溢出異常。
【第一次回收不可觸及的對象,第二次回收軟引用】

·軟引用通常用來實作記憶體敏感的緩存。
比如:高速緩存就有用到軟引用,如果還有空閑記憶體就可以暫時保留緩存,當記憶體不足時清理掉,這樣就保證了使用緩存的同僚,不會耗盡記憶體

·垃圾回收器在某個時候決定回收軟可達的對象的時候,會清理軟引用,并可選地把引用存放到一個引用隊列reference queue

eg:
User user = new User(1,"lee","male");//聲明一個強引用
SoftReference<User> sf = new SoftReference<User>(user);
user = null;//銷毀強引用
//此時user對應的new User就是一個軟引用
    
或
SoftReference<User> sr = new SoftReference<User>(new User(1,"lee","male"));
    
    
##三、弱引用weak reference:發現即回收
·弱引用也是用來描述那些非必須對象,被弱引用關聯的對象隻能生存島下一次垃圾收集發生
為止。在系統GC時,隻要發現弱引用,不管系統堆空間使用是否充足,都會回收掉隻被弱引用
關聯的對象。
    
·但是,由于垃圾回收器的線程通常優先級很低,是以,并不一定能很快地發現持有弱引用的
對象。在這種情況下,弱引用對象可以存在較長時間。

eg:
WeakReference<User> wr = new WeakReference<User>(new User(1,"lee","male"));
   
WeakHashMap就是實作了WeakReference    
    
##四、虛引用phantom reference:對象回收跟蹤
·一個對象是否有虛引用的存在,完全不會決定對象的生命周期。如果一個對象僅持有虛引用,
那麼它和沒有引用幾乎是一樣的,随時可能被垃圾回收器回收
    
·它不能單獨使用,也無法通過虛引用來擷取被引用的對象。當試圖通過虛引用的get()方法取
得對象時,總是NULL
    
·為一個對象設定虛引用關聯的唯一目的在于跟蹤垃圾回收過程。比如:能在這個對象被收集器
回收時收到一個系統通知
    
·由于虛引用可以跟蹤對象的回收時間,是以,也可以将一些資源釋放操作放置在虛引用中執行和記錄
    
eg:
ReferenceQueue rQueue = new ReferenceQueue();
PhantomReference<User> pr = new PhantomReference<User>(new User(1,"Lee","male"),rQueue);


##五、終結器引用Final Reference:包内可見
·它可以實作對象的finalize()方法,也可以成為終結器引用

·無需手動編碼,其内部配合引用隊列使用

·在GC是,終結器引用入隊,有Finalizer線程通過終結器引用找到被引用對象并調用它的
finalize()方法,第二次GC時才能回首被引用的對象

           

4、垃圾回收器

4.1、GC分類與性能名額

##一、垃圾回收分類
1、按照線程數分:(垃圾回收的線程數)

·"串行垃圾回收器" 和 "并行垃圾回收器"

·串行垃圾回收器指的是在同一時段内隻允許一個CPU用于執行垃圾回收操作,此時工作線程被暫停,直至垃圾收集工作結束。

·并行收集可以運用多個CPU同時執行垃圾回收,是以提升了應用的吞吐量,不過并行回收仍然和串行回收一樣,采用獨占式,使用STW機制
           
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)
2、按照工作模式分

·并發式的垃圾回收器 和 獨占式的垃圾回收器

·并發式垃圾回收器與應用程式線程交替工作,以盡可能減少應用程式的停頓時間

·獨占式垃圾回收器一旦運作,就停止應用程式中的所有使用者線程,直到垃圾回收過程完全結束
           
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)
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
           
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)
·為什麼要有這麼多垃圾收集器,因為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應用程式中是不
會采用串行垃圾收集器的
           
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)

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回收器
           
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)

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的比例、晉升老年代的對象年齡等參數會
被自動調整,以達到堆大小、吞吐量和停頓時間之間的平衡點
>在手動調整比較困難的場合,可以直接使用這種自适應的方式,僅指定虛拟機的最大堆、目标
的吞吐量和停段時間,讓虛拟機自己完成調優工作
           
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)

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收集器線程的影響,應用程式的性能在垃
圾回首階段可能會非常糟糕
           
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)

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。預設值是45


G1的設計原則就是簡化JVM性能調優,我們隻需三步即可完成調優:
1>開啟G1垃圾收集器-XX:+UseG1GC
2>設定堆的最大記憶體-Xms -Xmn
3>設定最大的停頓時間-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,這裡需要注意:是一部分老年代,而不是全部老年代。可以選擇那些old
region進行收集,進而可以對垃圾回收的耗時時間進行控制。也需要注意的是Mixed GC并不
是Full GC

>并發标記結束以後,老年代中百分百為垃圾的記憶體分段被回收了,部分為垃圾的記憶體分段被計
算了出來。預設情況下,這些老年代的記憶體分段會分8次被回收
>混合回收的收集器包括八分之一的來年代記憶體分段,Eden區記憶體分段,Survivor區記憶體分
段。混合回收的算法和年輕代回收的算法完全一樣,隻是回收集多了老年代的記憶體分段
>由于來年代中的記憶體分段預設分8次回收,G1會優先回收垃圾多的記憶體段,垃圾站村分段比例越
高,越先被回收
>混合回收并不一定要進行8次,有一個門檻值-XX:G1HeapWastePercent,預設為10%,意思
是允許整個堆記憶體中有10%的空間被浪費,意味着如果發現可以回收的垃圾占堆記憶體的比例低于
10%,則不再進行混合回收。因為GC會花費很多的時間但是會受到的記憶體卻很少


4、Full GC
G1的初衷是避免Full GC的出現,但是如果上述方式不能正常工作,G1會STW,使用單線程的記憶體回收算法進行垃圾回收,性能會非常差,應用程式停頓時間會很長
           
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)

4.9、垃圾回收器總結

·最小化地使用記憶體和并行開銷:選擇Serial GC + Serial Old
·最大化應用程式的吞吐量:選擇Parallel GC + Parallel Old
·最小化GC的中斷或停頓時間:選擇CMS GC + ParNew + Serial Old

·JDK9 廢棄了CMS  JDK14删除了CMS

(新生代大部分是複制算法  老年代大部分是标記-整理、标記-清除算法)
           
垃圾收集器 分類 作用位置 算法 特點 場景
Serial 串行 新生代 複制算法 響應速度 單CPU環境下的Client模式
ParNew 并行 新生代 複制算法 響應速度 多CPU環境Server模式,與CMS配合使用
Parallel 并行 新生代 複制算法 吞吐量 适用于背景運算且不需要太多的互動場景
Serial Old 串行 老年代 标記-壓縮算法 響應速度 單CPU環境下的Client模式
Parallel Old 并行 老年代 标記-壓縮算法 吞吐量 适用于背景運算且不需要太多的互動場景
CMS 并發 老年代 标記-清除算法 響應速度 适用于網際網路或B/S業務
G1 并發、并行 新生代、老年代 複制算法、标記-壓縮算法 響應速度 面向服務端應用

5.0、GC日志分析

##一、日志參數

-XX:+PrintGC         列印GC日志
-XX:+PrintGCDetails  列印日志詳情
-XX:+PrintGCTimeStamps  列印GC的時間戳(以基準時間形式)
-XX:+PrintGCDateStamps  列印GC的時間戳(以日期的形式)
-XX:+PrintHeapAtGC   在進行GC的前後列印出堆的資訊

-Xloggc:./logs/gc.log  日志檔案的輸出路徑

           
JVM系列之:記憶體與垃圾回收篇(三)JVM系列之:記憶體與垃圾回收篇(三)
##二、GC日志分析

GC  表示隻在"新生代"上進行
Full GC 包括"新生代""老年代""元空間" (會發生STW)

PSYoungen :  Parallel Scavenge收集器新生代的名稱
DefNew : 使用了Serial收集器新生代的名稱  Default New Generation
ParNew :ParNew收集器在新生代的名稱  Parallel New Generation
garbage-first heap : G1收集器
ParOldGen : Parallel Old

Allocation 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的記憶體布局、可并發的标記壓縮算法、低延遲為目标]
[并發标記-并發與被重配置設定-并發重配置設定-并發重映射]
           

繼續閱讀