GC知識記錄

二、關於Minor GC,Major GC與Full GC

1)  Minor GC:即新生代的GC,指發生在新生代的垃圾收集動做。當新生代的Eden區內存不足時,就會觸發Minor GC。因爲對象建立時,都會在Eden區分配內存,所以經過日誌能夠看到Minor GC動做執行至關頻繁;同時,因爲新生代對象朝生夕亡的特性,每次Minor GC的效果都十分理想。此外,Minor GC的效率也是很是高的。html

2)Major GC與Full GC:咱們能夠認爲Major GC與Full GC是一個概念,是指發生在老年代的引起了STW的GC。出現Full GC時,常常會伴隨着至少一次的Minor GC,是因爲老年代不少對象都會引用到新生代的對象,先進行一次Minor GC能夠提升老年代GC的速度。通常Full GC會比Minor GC速度慢10倍以上。java

 

三、垃圾收集算法以及垃圾收集器

3.1  如何判斷對象存活以及永久代可回收的判斷

   在進行垃圾收集以前,須要作的一件事情就是判斷對象對象是否存活。如何判斷對象存活,通常有兩種方法:算法

  一、引用計數法:引用計數法實現方式很簡單,首先給對象添加一個引用計數器,每當有一個地方引用它時,計數器加1;當每有一個地方引用失效時,計數器減1;當到達零時,表示沒有任何地方有對這個對象的引用了,該對象即爲垃圾回收的目標對象。但是目前沒有商業的JVM使用引用計數法去判斷對象是否存活,緣由是由於它有一個弊端,即當某兩個對象存在循環引用時,引用計數器永遠都不能到零,所以也不會通知垃圾收集器進行回收。markdown

  二、可達性分析算法:目前商業虛擬機的主流實現中,都經過該種方式判斷對象是否可回收。主要思路是定義一系列叫作「GC Root」的根節點,從這些根節點往下搜索,走過的路徑稱爲引用鏈,當一個對象到「GC Root」沒有任何引用鏈相連的時候,則證實該對象是不可用的。以下圖所示,obj5與obj6即爲可回收的對象。由此就能夠很好的解決循環引用的問題。多線程

blob.png

經過上面的方法便可以判斷出新生代以及年老代的對象是否能夠回收,可是還有一個比較特殊的區域,那就是永久代。永久代同樣是須要進行垃圾回收的,可是在永久代中進行垃圾回收的效果可能會比新生代以及老年代差了許多。永久代中保存的主要是已被虛擬機加載的類信息以及常量信息,所以永久代中垃圾回收主要包括兩個部分:廢棄的常量和無用的類。併發

一、廢棄的常量:判斷常量是否已廢棄的方法與判斷對象是否存活的方式相似,即發現若是沒有任何對象引用常量池中的常量時,便可判定該常量是能夠被回收的。函數

二、無用的類:判斷一個類是否無用會比較複雜一點,須要從如下幾個方面進行判斷:spa

  • 該類全部的實例已經被回收,也就是java堆中不存在該類的任何實例線程

  • 加載該類的ClassLoader已經被回收指針

  • 該類對應的java.lang.Class對象沒有在任何地方被引用,沒法在任何地方經過反射機制訪問該類的方法

     

3.2 垃圾收集算法思想

一、複製算法: 現代商業虛擬機都使用這種方法來回收新生代。該算法的主要思路是:將內存分爲兩塊,對象建立都在某一塊上分配內存,當該塊內存快滿時觸發垃圾回收,回收時將該塊內存中存活的對象所有復制到另一塊內存中,而後將這塊內存所有回收掉。這種方法的好處是因爲是整塊內存的回收,所以不會產生內存碎片。 現代商業虛擬機都是將內存分爲三塊,就是咱們平時所說的1個Eden區以及2個Survivor區,因爲新生代對象通常都是朝生夕亡(98%亡),因此通常默認比例8:1:1。垃圾回收時,將Eden區以及使用的1個Survivor區上存活的對象複製到另外一個Survivor區上,而後清空Eden與以前的那個Survivor區。

二、標記清除算法:該算法的主要思路是:先標記出全部須要被回收的對象,在標記完成後統一回收被標記的對象,標記過程就是前面介紹的判斷對象不存活了就標記。 該方法缺點也很明顯,就是會產生大量的內存碎片,當有大對象進來時,可能整體空間是足夠的,可是確找不到這樣一塊連續的內存空間而出現OOM異常。

三、標記整理/標記壓縮算法: 該算法的主要思路是:先標記出全部須要被回收的對象,標記過程與上面一致,可是不是標記完就回收,而是讓沒有被標記的對象(存活的對象)所有向一端移動,最後清理掉邊界之外的內存。該算法常被用來進行老年代的垃圾回收。

 

3.3 HotSpot虛擬機垃圾收集器

一、新生代收集器

  •    Serial收集器:最基本的,歷史最悠久的收集器。單線程收集器,在進行垃圾回收時必須暫停掉全部的用戶線程,即Stop The World。可是它也有一個優勢就是簡單高效。採用的是複製算法,經過-XX:+UseSerialGC配置。

  •    ParNew收集器:其實就是Serial收集器的多線程版本,在單CPU的狀況下效果不必定會比Serial好。可是他的優點是能夠配合CMS收集器進行工做,採用的是複製算法。經過-XX:+UseParNewGc配置

  •    Parallel Scavenge收集器:Parallel Scavenge也是一款多線程收集器,與ParNew的不一樣之處在於關注點不同,其餘收集器主要關注儘可能下降STW的時間,而它主要關注在吞吐量,採用的是複製算法。經過-XX:+UseParallelGC配置

二、老年代收集器

  •    Serial Old收集器:是Serial收集器的老年代版本,採用的是「標記整理/標記壓縮算法」,經過-XX:+UseSerialOldGC配置

  •    Parallel Old收集器:是Parallel Scavenge收集器的老年代版本,採用多線程以及「標記整理/標記壓縮算法」。經過-XX:+UseParallelOldGC配置。

  •    CMS 收集器:這時一款以獲取最短回收停頓爲目標的收集器,CMS是Concurrent Mark Sweep的縮寫,從名字能夠看到,這時一款使用「標記-清理」算法的併發收集器。主要分爲:初始標記(CMS initial mark),併發標記(CMS concurrent mark),從新標記(CMS remark),併發清除(CMS concurrent sweep)4步,其中初始標記與從新標記兩步仍然會致使‘Stop The World’,可是時間會比以前的收集器短許多。經過-XX:+UseConcMarkSweepGC配置

 三、G1(GarbageFirst)收集器:當前收集器發展的最前沿的成果之一,能充分利用多CPU的硬件優點,來縮短STW的時間,能夠不須要其餘收集器的配合就能夠管理整個堆內存,它最大的一個優點就是可預測停頓。

 

四、關於直接內存(Direct Memory)的GC

 

Direct Memory垃圾回收機制DirectByteBuffer所佔內存是在堆內存以外的,所以一臺機器堆內存分配的越多,會致使可用的堆外內存空間越少。Direct Memory的GC不能像新生代與老年代的GC同樣,發現空間不足了就通知收集器進行垃圾回收,他只能等待老年代滿了之後進行的Full GC順便把他的廢棄的內存回收掉,不然它只能等到拋出內存溢出的異常時,進入catch分支執行System.gc了,特別是若是設置了-XX:+DisableExplicit的話System.gc也會失效,注意,System.gc()執行的效果以下:

1
2
3
4
5
6
[Full GC [PSYoungGen: 0K->0K(306176K)] 
          [ParOldGen: 526K->526K(699392K)] 
          526K->526K(1005568K) 
          [PSPermGen: 2636K->2636K(262144K)]
0.0045990  secs] 
[Times: user= 0.01  sys= 0.00 , real= 0.00  secs]

也就是說,System.gc()執行的是一次Full GC。

下面證實Direct Memory是受GC控制的,例如ByteBuffer bb = ByteBuffer.allocateDirect(1024),這段代碼的執行會在堆外佔用1k的內存,Java堆內只會佔用一個對象的指針引用的大小,堆外的這1k的空間只有當bb對象被回收時,纔會被回收,這裏會發現一個明顯的不對稱現象,就是堆外可能佔用了不少,而堆內沒佔用多少,致使還沒觸發GC,那就很容易出現Direct Memory形成物理內存耗光。

  • 堆外內存的配置

堆外內存使用的配置以下:

  -XX:MaxDirectMemorySize=40M

下面經過實例來看下堆外內存的回收機制。

A、設置-verbose:gc -XX:+PrintGCDetails -XX:MaxDirectMemorySize=40M,執行以下代碼:

1
2
3
  while  ( true ) {
             ByteBuffer buffer = ByteBuffer.allocate( 10  1024  1024 );
         }

執行結果以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[GC [PSYoungGen: 256266K->668K(306176K)] 256266K->676K(1005568K),  0.0019780  secs] [Times: user= 0.00  sys= 0.00 , real= 0.00  secs] 
[GC [PSYoungGen: 262030K->620K(306176K)] 262038K->636K(1005568K),  0.0011910  secs] [Times: user= 0.01  sys= 0.00 , real= 0.00  secs] 
[GC [PSYoungGen: 260143K->620K(306176K)] 260159K->636K(1005568K),  0.0014380  secs] [Times: user= 0.00  sys= 0.00 , real= 0.00  secs] 
[GC [PSYoungGen: 257778K->588K(306176K)] 257794K->604K(1005568K),  0.0011710  secs] [Times: user= 0.00  sys= 0.00 , real= 0.00  secs] 
[GC [PSYoungGen: 257349K->572K(306176K)] 257365K->588K(1005568K),  0.0012910  secs] [Times: user= 0.01  sys= 0.00 , real= 0.00  secs] 
[GC [PSYoungGen: 257073K->588K(348672K)] 257089K->604K(1048064K),  0.0014530  secs] [Times: user= 0.00  sys= 0.00 , real= 0.00  secs] 
[GC [PSYoungGen: 338944K->32K(348672K)] 338960K->593K(1048064K),  0.0012450  secs] [Times: user= 0.00  sys= 0.00 , real= 0.00  secs] 
[GC [PSYoungGen: 338238K->32K(348672K)] 338799K->593K(1048064K),  0.0004050  secs] [Times: user= 0.01  sys= 0.00 , real= 0.00  secs] 
[GC [PSYoungGen: 338140K->32K(348672K)] 338701K->593K(1048064K),  0.0004250  secs] [Times: user= 0.00  sys= 0.00 , real= 0.00  secs] 
[GC [PSYoungGen: 338075K->32K(348672K)] 338636K->593K(1048064K),  0.0004440  secs] [Times: user= 0.00  sys= 0.00 , real= 0.01  secs] 
[GC [PSYoungGen: 338033K->32K(348672K)] 338594K->593K(1048064K),  0.0004340  secs] [Times: user= 0.00  sys= 0.00 , real= 0.00  secs] 
[GC [PSYoungGen: 338005K->32K(348672K)] 338566K->593K(1048064K),  0.0004750  secs] [Times: user= 0.00  sys= 0.00 , real= 0.00  secs] 
[GC [PSYoungGen: 337987K->32K(348672K)] 338548K->593K(1048064K),  0.0004450  secs] [Times: user= 0.00  sys= 0.00 , real= 0.00  secs] 
[GC [PSYoungGen: 337975K->32K(348672K)] 338536K->593K(1048064K),  0.0004340  secs] [Times: user= 0.00  sys= 0.00 , real= 0.00  secs] 
[GC [PSYoungGen: 337967K->32K(348672K)] 338528K->593K(1048064K),  0.0004620  secs] [Times: user= 0.00  sys= 0.00 , real= 0.01  secs] 
[GC [PSYoungGen: 337962K->32K(348672K)] 338523K->593K(1048064K),  0.0003790  secs] [Times: user= 0.00  sys= 0.00 , real= 0.00  secs]

運行這段代碼會發現:程序能夠一直運行下去,不會報OutOfMemoryError。若是使用了-verbose:gc -XX:+PrintGCDetails,會發現程序頻繁的進行垃圾回收活動。

B、從新設置啓動參數爲:-verbose:gc -XX:+PrintGCDetails -XX:+DisableExplicitGC -XX:MaxDirectMemorySize=40M, 與以前的JVM啓動參數相比,增長了-XX:+DisableExplicitGC,這個參數做用是禁止代碼中顯示調用GC。代碼如何顯示調用GC呢,經過System.gc()函數調用。若是加上了這個JVM啓動參數,那麼代碼中調用System.gc()沒有任何效果,至關因而沒有這行代碼同樣。執行一樣代碼,執行效果以下:

image

顯然堆內存(包括新生代和老年代)內存很充足,可是堆外內存溢出了。也就是說NIO直接內存的回收,須要依賴於System.gc()。若是咱們的應用中使用了java nio中的direct memory,那麼使用-XX:+DisableExplicitGC必定要當心,存在潛在的內存泄露風險。

五、使用jstat命令查看線上堆內存以及GC狀況

 

  • 命令格式:jstat -gcutil LVMID 間隔時間 執行次數( 經過jps命令可拿到LVMID)

     

說明:
  S0 表示Survivor0使用的比例,有時表示From,有時表示To

  S1 表示Survivor1使用的比例,有時表示From,有時表示To

  E 表示Eden區使用的比例

  O 表示Old區即老年代使用的比例

  P 表示Perm區即永久代或者方法區已使用的比例

  YGC 表示程序啓動以來發生的Minor GC(Young GC)的總次數

  YGCT 表示Minor GC的總耗時

  FGC 表示程序啓動以來發生的Full GC的總次數

  FGCT 表示Full GC的總耗時

  GCT 表示GC總耗時

 

附:jstat命令其餘選項

1
2
3
4
5
6
7
8
9
-class           監視類裝載、卸載數量,總空間以及裝載類的耗時
-gc              監視整個java堆的情況,包括Eden區、兩個Survivor區以及新生代與老年代的容量,已用空間以及GC總耗時等信息。
-gcutil          與上面gc相似,可是gcutil主要關注百分比
-gccause         與gcutil相似,可是增長了致使上次gc發生的緣由
-gcnew           監視新生代GC的情況
-gcnewcapacity   與gcnew相似,輸出主要關注使用到的最大、最小空間
-gcold           監視老年代GC的情況
-gcoldcapacity   與gcold相似,輸出主要關注使用到的最大、最小空間
-gcpermcapacity  輸出永久代使用到的最大、最小空間
相關文章
相關標籤/搜索