1. 程式人生 > >JVM虛擬機器知識整理

JVM虛擬機器知識整理

Java記憶體申請:
  1.靜態記憶體:編譯時就能夠確定的記憶體就是靜態記憶體,記憶體是固定的,系統一次性分配,比如int內型;
  2.動態記憶體:就是程式執行時才知道要分配的儲存空間大小,比如Java物件的記憶體空間。
    垃圾回收:Java堆和方法區不一樣,我們只有在程式執行期間才知道會建立那些物件,所以這部分的記憶體的分配和回收都是動態的。一版我們所說的垃圾回收也是針對的這一部分。
    總之Stack的記憶體管理是順序分配的,而且定長,不存在記憶體回收問題;而Heap 則是為java物件的例項隨機分配記憶體,不定長度,所以存在記憶體分配和回收的問題
垃圾檢測、回收演算法:
   1.垃圾檢測 
      a.引用計數法:給一個物件新增引用計數器,每當有個地方引用它,計數器就加1;引用失效就減1
      b.可達性分析演算法:以根集物件為起始點進行搜尋,如果有物件不可達的話,即是垃圾物件。這裡的根集一般包括java棧中引用的物件、方法區常良池中引用的物件,本地方法中引用的物件等。
   2.回收演算法
      a.標記-清除(Mark-sweep);
         不足:效率低;標記清除之後會產生大量碎片。
      b.複製(copying)
         不足:需要兩倍記憶體空間
      c.標記整理(Mark-compact)
         此演算法結合了“標記-清除”和“複製”兩個演算法的優點。也是分兩階段,第一階段從根節點開始標記所有被引用物件,第二階段遍歷整個堆,清除未標記物件並且把存活物件“壓縮”到堆的其中一塊,按順序排放。此演算法避免了“標記-清除”的碎片問題,同時也避免了“複製”演算法的空間問題。      
      d.分代收集演算法
         這是當前商業虛擬機器常用的垃圾收集演算法。分代的垃圾回收策略,是基於這樣一個事實:不同的物件的生命週期是不一樣的。因此,不同生命週期的物件可以採取不同的收集方式,以便提高回收效率。
   
    記憶體分為年輕代,年老代,持久代
    年輕代分為一個Eden區,兩個survivor區
    年輕代:涉及了複製演算法;年老代:涉及了“標記-整理(Mark-Sweep)”的演算法。通常會在老年代記憶體被佔滿時將會觸發Full GC,回收整個堆記憶體。

垃圾收集器:
   新生代收集器:
   1.Serial收集器:Serial(序列)收集器是最基本、發展歷史最悠久的收集器,它是採用複製演算法的新生代收集器,曾經(JDK 1.3.1之前)是虛擬機器新生代收集的唯一選擇。它是一個單執行緒收集器,但“單執行緒”並非指該收集器只會使用一個CPU或一條收集執行緒去完成垃圾收集工作,更重要的是它在進行垃圾收集時,必須暫停其他所有的工作執行緒,直至Serial收集器收集結束為止(“Stop The World”)。這項工作是由虛擬機器在後臺自動發起和自動完成的,在使用者不可見的情況下把使用者正常工作的執行緒全部停掉,這對很多應用來說是難以接收的。
   2.ParNew收集器:

ParNew收集器就是Serial收集器的多執行緒版本,它也是一個新生代收集器。除了使用多執行緒進行垃圾收集外,其餘行為包括Serial收集器可用的所有控制引數、收集演算法(複製演算法)、Stop The World、物件分配規則、回收策略等與Serial收集器完全相同,兩者共用了相當多的程式碼。
   3.Parallel Scavenge收集器:Parallel Scavenge收集器也是一個新生代收集器,它也是使用複製演算法的收集器,又是並行多執行緒收集器。parallel Scavenge收集器的特點是它的關注點與其他收集器不同,CMS等收集器的關注點是儘可能地縮短垃圾收集時使用者執行緒的停頓時間,而parallel Scavenge收集器的目標則是達到一個可控制的吞吐量。吞吐量= 程式執行時間/(程式執行時間 + 垃圾收集時間),虛擬機器總共運行了100分鐘。其中垃圾收集花掉1分鐘,那吞吐量就是99%。
   4.Serial Old(序列GC)收集器:
Serial Old是Serial收集器的老年代版本,它同樣使用一個單執行緒執行收集,使用“標記-整理”演算法。主要使用在Client模式下的虛擬機器。
   5.Parallel Old(並行GC)收集器:Parallel Old是Parallel Scavenge收集器的老年代版本,使用多執行緒和“標記-整理”演算法。
   6.CMS(併發GC)收集器:CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。CMS收集器是基於“標記-清除”演算法實現的,整個收集過程大致分為4個步驟:
     a.初始標記(CMS initial mark)
     b.併發標記(CMS concurrenr mark)
     c.重新標記(CMS remark)
     d.併發清除(CMS concurrent sweep)
     CMS收集器的優點:併發收集、低停頓,但是CMS還遠遠達不到完美,器主要有三個顯著缺點:
CMS收集器對CPU資源非常敏感。在併發階段,雖然不會導致使用者執行緒停頓,但是會佔用CPU資源而導致引用程式變慢,總吞吐量下降。CMS預設啟動的回收執行緒數是:(CPU數量+3) / 4。
CMS收集器無法處理浮動垃圾,可能出現“Concurrent Mode Failure“,失敗後而導致另一次Full  GC的產生。由於CMS併發清理階段使用者執行緒還在執行,伴隨程式的執行自熱會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之後,CMS無法在本次收集中處理它們,只好留待下一次GC時將其清理掉。這一部分垃圾稱為“浮動垃圾”。也是由於在垃圾收集階段使用者執行緒還需要執行,
即需要預留足夠的記憶體空間給使用者執行緒使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留一部分記憶體空間提供併發收集時的程式運作使用。在預設設定下,CMS收集器在老年代使用了68%的空間時就會被啟用,也可以通過引數-XX:CMSInitiatingOccupancyFraction的值來提供觸發百分比,以降低記憶體回收次數提高效能。要是CMS執行期間預留的記憶體無法滿足程式其他執行緒需要,就會出現“Concurrent Mode Failure”失敗,這時候虛擬機器將啟動後備預案:臨時啟用Serial Old收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了。所以說引數-XX:CMSInitiatingOccupancyFraction設定的過高將會很容易導致“Concurrent Mode Failure”失敗,效能反而降低。
     最後一個缺點,CMS是基於“標記-清除”演算法實現的收集器,使用“標記-清除”演算法收集後,會產生大量碎片。空間碎片太多時,將會給物件分配帶來很多麻煩,比如說大物件,記憶體空間找不到連續的空間來分配不得不提前觸發一次Full  GC。為了解決這個問題,CMS收集器提供了一個-XX:UseCMSCompactAtFullCollection開關引數,用於在Full  GC之後增加一個碎片整理過程,還可通過-XX:CMSFullGCBeforeCompaction引數設定執行多少次不壓縮的Full  GC之後,跟著來一次碎片整理過程。
   7.G1收集器:G1(Garbage First)收集器是JDK1.7提供的一個新收集器,G1收集器基於“標記-整理”演算法實現,也就是說不會產生記憶體碎片。還有一個特點之前的收集器進行收集的範圍都是整個新生代或老年代,而G1將整個Java堆(包括新生代,老年代)。
     如果不計算維護Remembered Set的操作,G1收集器的運作大致可劃分為以下幾個步驟:
     a.初始標記(Initial Marking) 僅僅只是標記一下GC Roots 能直接關聯到的物件,並且修改TAMS(Nest Top Mark Start)的值,讓下一階段使用者程式併發執行時,能在正確可以的Region中建立物件,此階段需要停頓執行緒,但耗時很短。
     b.併發標記(Concurrent Marking) 從GC Root 開始對堆中物件進行可達性分析,找到存活物件,此階段耗時較長,但可與使用者程式併發執行。
     c.最終標記(Final Marking) 為了修正在併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分標記記錄,虛擬機器將這段時間物件變化記錄線上程的Remembered Set Logs裡面,最終標記階段需要把Remembered Set Logs的資料合併到Remembered Set中,這階段需要停頓執行緒,但是可並行執行。
     d.篩選回收(Live Data Counting and Evacuation) 首先對各個Region中的回收價值和成本進行排序,根據使用者所期望的GC 停頓是時間來制定回收計劃。此階段其實也可以做到與使用者程式一起併發執行,但是因為只回收一部分Region,時間是使用者可控制的,而且停頓使用者執行緒將大幅度提高收集效率。

   新生代GC方式  老年代和持久代GC方式
Client  Serial 序列GC       Serial Old 序列GC
Server  Parallel Scavenge  並行回收GC    Parallel Old 並行GC