java學習筆記-4 JVM垃圾回收(GC)

引言

jvm垃圾回收相關的問題是老生常談的問題了,相信你們都有所瞭解,這裏再進行相關的探討,以加深理解。若文中有不正之言,望不吝指正。java

本文將圍繞如下幾個點展開算法

1.爲何要進行垃圾回收

咱們知道jvm的內存結構中,依賴內存是否可共享,將內存劃分爲線程專享區和線程共享區,其中程序計數器、虛擬機棧、本地方法棧做爲線程獨享的內存,生命週期跟線程相關,好比棧中的棧幀須要分配多少內存通常在類結構肯定時就已經肯定好了,線程結束時內存就跟着回收了,不須要咱們過多的關心垃圾回收。可是做爲線程共享的內存區域,堆和方法區,java new出來的對象大部分是在堆中分配的,並且只有在程序運行期間才能知道會建立出哪些對象,在這部份內存的分配和回收是咱們關心的。其實歸根結底就是一句話,知道底層的jvm怎麼進行垃圾回收的,對咱們排查內存溢出和泄露的問題有很大幫助,並且對於提升程序的性能也有很大幫助。
複製代碼

2.哪些內存須要回收

判斷哪些內存須要回收有兩種經典的算法
  1.引用計數器法
     引用計數器法的實現是給對象添加一個引用計數器,當對象被引用時,計數器值就+1,對象引用失效時計數器值-1,當引用計數器值爲0時,可認爲對象無引用,此時可被回收。可是這個算法不能解決對象循環引用的問題,因此虛擬機並非採用這種方式去判斷是否回收對象的。
  2.可達性分析算法
    可達性分析算法是將可做爲GC Roots的對象做爲起始點,展開搜索,搜索所通過的路徑,對一個對象到GC Roots 沒有任何引用,則意味着對象不可達,此時對象可被回收。
複製代碼

其中的GCRoots,其實就是一組 必須活躍的引用,大體包括如下幾種

  1. 虛擬機棧中引用的對象
  2. 方法區中靜態屬性引用的變量
  3. 方法區中常量引用的對象
  4. 本地方法棧中引用的對象

方法區垃圾回收:其實方法區中也是能夠進行垃圾回收的,只是回收的性價比很低,就是說執行一次垃圾回收的動做並不能回收到不少的空間。可是知足必定條件仍是能夠進行回收的,通常回收的是無用的類和廢棄常量。其中判斷常量是否廢棄,沒有引用便可;而判斷類無用需知足 1.java堆中沒有任何該類的實例;2.加載該類的類加載器已被回收;3.該類的java.lang.Class對象沒有在任何地方被引用 ,不能經過反射獲取該類的方法。多線程

3.什麼時候進行垃圾回收

即觸發GC的時間,在新生代的Eden區滿了,會觸發新生代GC(MinorGC),通過屢次觸發新生代GC存活下來的對象就會升級到老年代,升級到老年代的對象所需的內存大於老年代剩餘的內存,則會觸發老年代GC(FullGC)。當程序調用System.gc()時也會觸發Full GC。
複製代碼

4. 垃圾收集算法

1. 標記清除算法
    見名知意,該算法分爲兩個階段,標記和清除。以下圖所示,標記就是根據以前的GCRoots判斷對象是否還有引用可達。可是這個算法的缺點也很明顯,第一就是標記和清除效率都很低;第二就是這種算法會形成大量不連續的內存碎片的存在,當程序須要分配大的內存空間給對象的時候,極可能由於沒法找到連續的大的內存空間,再一次觸發GC。圖以下:
複製代碼

圖中對象b沒有引用,會被回收
圖中能夠看出,回收後有大量不連續的內存空間。

2.複製算法
    基於上一個算法,爲了解決效率問題,引伸出「複製」算法。基本思想就是將內存劃分爲大小相等的兩塊,每次只使用其中的一塊,當一塊用完將整個活着的對象所有複製到另外一個半區,此時不須要考慮內存碎片的問題,只需移動指針便可,簡單高效。可是須要浪費一半的內存。因爲java堆內存對象存活的特色,大部分新生代中的對象存活時間都比較短,因此主流的虛擬機會將內存分爲一塊較大的Eden區和兩塊較小的Survivor區,比例是8:1:1。每次分配對象到Eden區和一個Suivivor區,垃圾回收時將整個Eden區和剛纔用到的Survivor區存活的對象一次性「複製」到未被使用的那個Survivor區,最後清理掉Eden區和以前的分配的Survivor區。可是並不能保證在將Eden去和Survivor區複製到另外一個Survivor區的時候內存空間必定是充足的,此時須要依賴其它內存進行分配擔保。分配擔保策略指明,當Eden區和以前的Survivor區之存活的對象複製到另外一塊Survivor區時,內存空間若不夠,則直接將內存分配到老年代。
複製代碼

複製的時候只需移動相應的指針便可

3.標記整理算法
     從上面的「複製」算法能夠得知,當對象的存活率很高時,進行復制操做的時候效率將會變低。這時有人提出標記整理算法,標記過程較以前相同,只是後面不直接對對象進行清除,而是將存活的對象都向一邊移動,並更改對應的指針,而後清理掉右端之外的內存。可是這個算法效率也不高由於,由於包括了標記 + 移動,可是也解決了內存碎片的問題。
複製代碼

4.分代收集算法

其實就是上面算法的綜合。根據對象的存活週期,通常把java堆分爲新生代和老生代。新生代中對象存活率較低,選用複製算法,只需付出少許存活對象的複製成本便可完成收集;老年代中對象存活率高,沒有額外的內控空間進行擔保,必須使用標記清除或者標記整理算法來進行垃圾回收。
複製代碼

5.幾種垃圾收集器比較

在進行比較以前先介紹下衡量的參數併發

  1. 吞吐量: 所謂吞吐量就是 CPU 用於運行代碼的時間與 CPU 總消耗時間的比值,即吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間)jvm

  2. 停頓時間 :jvm在運行垃圾回收時,需停頓用戶程序,這裏指的是停頓用戶程序的時常高併發

Serial收集器

單線程的收集器,採用 複製算法,進行垃圾回收時須要將用戶的全部線程所有暫停直到垃圾回收動做的完成。交互體驗不是很好,可是在單核cpu或者虛擬機運行在client模式下,停頓時間能夠控制到毫秒級,是能夠接受。
複製代碼

Serial Old收集器

Serial Old是Serial的老年代版本,使用   標記整理算法 
複製代碼

ParNew收集器

ParNew收集器採用複製算法,其實就是Serial收集器的多線程版本,是server模式下首選的新生代收集器。在多核cpu下有着比Serial更好的表現。
複製代碼

Parallel Scavenge收集器

採用複製算法,也是一個新生代的收集器。較於ParNew收集器,Parallel Scavenge收集器更多的目標是達到一個可控制的吞吐量。Parallel Scavenge收集器提供了兩個參數用於精確控制吞吐量,分別是控制最大垃圾收集 停頓時間的-XX:MaxGCPauseMillis參數以及直接設置吞吐量大小的-XX:GCTimeRatio參 數
複製代碼

Parallel Old收集器

Parallel Old收集器採用  標記整理算法 是ParallelScavenge收集器的老年代版本。在注重吞吐量以及cpu資源敏感的場合,均可以優先考慮Parallel Scavenge加Parallel Old收集器。
複製代碼

CMS(Concurrent Mark Sweep)收集器

採用標記清除算法,是一個老年代的並行收集器,是一個以獲取最短回收停頓時間爲目標的處理器,具備高併發、低停頓的特色。包括四個過程:初始標記、併發標記、從新標記、併發清除。其中初始標記和從新標記都須要暫停用戶全部線程。
複製代碼

可是這個收集器也有明顯的缺點, 1. 前面介紹過的標記清除算法講到會產生大量的內存碎片,極可能老年代空間有富餘,可是新生代沒有足夠大的內存空間分配對象。CMS收集器提供了一個-XX:+UseCMSCompactAtFullCollection開 關參數(默認就是開啓的),用於在CMS收集器頂不住要進行FullGC時開啓內存碎片的合併 整理過程,內存整理的過程是沒法併發的,空間碎片問題沒有了,但停頓時間不得不變長; 2.CMS收集器沒法處理浮動垃圾(因爲cms併發清理階段用戶線程還在運行,這段時間內出現的新的垃圾稱之爲浮動垃圾) 3.cms對cpu的資源很敏感,雖然收集器是併發的,可是由於佔用了一部分線程會致使應用程序變慢,總吞吐量下降。

G1收集器

G1收集器基於 標記整理算法 是面向服務端的,是面向新生代和老生代的收集器。收集過程主要包括:1.初始標記:標記GCRoots能關聯的對象;
2.併發標記:從GC Roots開始對對象進行可達性分析,找出存活的對象,耗時較久,可與用戶線程同時進行 3.最終標記:修正在併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分標記記錄 4.篩選回收:首先對各個region的回收價值和成本進行排序,根據用戶所指望的停頓時間來制定回收計劃,優先處理最須要回收的。 
複製代碼

收集器參數總結

引用

《深刻理解java虛擬機》 周志明性能

相關文章
相關標籤/搜索