Java虛擬機學習(四)

垃圾回收(Garbage Collection, GC)主要須要考慮的問題:java

  • 哪些內存須要回收?
  • 何時回收?
  • 如何回收?

哪些內存須要回收

虛擬機棧、本地方法棧、程序計數器因爲是線程獨有,且棧幀的內存大小在類結構肯定時已經相對肯定因此不須要考慮回收問題,方法結束或線程結束天然回收。須要回收的是Java堆以及方法區算法

 

何時回收以及如何回收

在一個對象不可能再被任何途徑使用時回收;數組

微軟的COM(Component Object Model)技術、使用ActionScript3的FlashPlayer、Python語言和Squirrel使用了引用計數算法進行內存管理,而主流商業語言(Java、C#、Lisp等)主流實現算法都是可達性分析算法。多線程

  1. 引用計數算法(Reference Counting):給對象添加一個引用計數器,當有一個地方引用它時,計數器加1,引用失效計數器減1,當計數器爲0時,表示此對象再也不被引用,能夠回收;缺點:沒法解決對象相互循環引用問題
  2. 可達性分析算法(Reachability Analysis):經過一系列稱爲「GC Roots」的對象做爲起始點,從這個點開始向下搜索,所走的路徑稱爲引用鏈(Reference Chain),當一個對象沒有任何引用鏈也GC Roots相連時,將對對象進行第一次標記並經過該對象的分析(1.是否重寫了finalize()方法,2.finalize()方法有沒有被虛擬機調用過),若是重寫了方法且沒有被調用過將會被放置到一個叫作F-Queue的隊列中,稍後將被逐個執行finalize()方法,以後虛擬機將對這些對象進行二次標記,若是在執行了finalize()方法後,可達性分析能夠經過將被移出「待回收」集合繼續存活。

回收方法區

回收運行時常量池,與Java堆對象回收相似併發

回收無用類,知足一下3個條件纔算是「無用類」:oracle

  1. 該類的全部實例已被回收,即在Java堆中再也不存在該類實例
  2. 加載該類的ClassLoader已經被回收
  3. 該類對應的java.lang.Class對象沒有任何地方被引用,無任何地方經過反射訪問該類的方法

可使用-Xnoclassgc關閉這部分的回收,還可使用-verbose:class以及-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看類加載和卸載信息。優化

垃圾回收算法

  1. 標記-清除算法:
    1. 基本算法:判斷每一個對象是否應該回收,須要回收的標記,最後一塊兒清除;缺點:會出現不少不連續空內存,標記和清除小女都不高
    2. 衍生:複製算法:用一半內存空着,標記完後,將沒標記的複製到空內存,清除以前全部;缺點:只有一半內存可使用
    3. 優化複製算法:將內存分爲了80%的Eden以及兩塊10%的Survivor,每次使用90%內存,10%用於留空複製,不夠像老年代借
  2. 標記-整理算法
    1. 基本算法:標記和上同,將存活的對象移動到前面,清除後面的內存
  3. 分代算法:根據對象存活週期的不一樣將內存分爲幾塊,通常把Java堆分爲新生代和老年代,新生代中每次有大量的對象死亡,因此使用優化的複製算法;而老年代存活率高,因此使用「標記-清除」或者「標記-整理」算法

垃圾回收器

Java虛擬機規範中堆垃圾收集器應該如何實現並無明確的規定,針對JDK1.7的Hotspot虛擬機,實現了其中不一樣的垃圾回收器:ui

圖片來源(https://blogs.oracle.com/jonthecollector/entry/our_collectors),上圖中的連線兩端的垃圾回收器能夠配合使用spa

  1. Serial收集器:單線程的垃圾收集器,當使用它收集垃圾時,必須中止其餘全部線程,曾經是JDK1.3.1以前的惟一新生代垃圾會回收器,client版本JVM的默認選擇
  2. ParNew(new parallel)收集器:Serial的多線程版本,與Serial公用了大量相同的算法,Server版本JVM新生代的首選,參數: -XX:+UserParNewGC
  3. CMS(Concurrent Mark Sweep)收集器:第一個實現了讓垃圾回收線程與用戶回收線程(基本上)同時工做的收集器,通常用於老年代收集,參數: -XX:+UseConcMarkSweepGC
  4. Parallel Scavenge收集器:新生代多線程收集器,使用複製算法,他與上面收集器的注重點不一樣,上面收集器注重點是在儘可能減小垃圾收集時用戶線程的停頓時間,而次收集器注重達到一個可控的吞吐量(throughput)。經過兩個參數精確控制吞吐量:-XX:MaxGCPauseMillis設置最大停頓時間、-XX:GCTimeRatio直接設置吞吐量大小。還有一個開關參數參數-XX:+UserAdaptiveSizePolicy,打開後能夠不用手動設置新生代大小(-Xmn)、Eden與Survivor區比例(-XX:SurvivorRatio)、晉升老年代對象年齡(-XX:PretenureSizeThreshold)
  5. Serial Old:是Serial的老年代版本,使用「標記-整理」算法
  6. Parallel Old:是Parallel Scavenge的老年代版本,使用「標記-整理」算法

*吞吐量(Throughput) = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間)線程

CMS(Concurrent Mark Sweep)收集器是以得到最短回收停頓時間爲目的的收集器,從名字能夠看出CMS是基於「標記-清除」算法實現的,主要分爲四個步驟:

  1. 初始標記(CMS Initial mark)
  2. 併發標記(CMS Concurrent mark)
  3. 從新標記(CMS remark)
  4. 併發清除(CMS Concurrent sweep)

其中初始標記會中止全部用戶進程,標記出全部與GC Roots直接相連的對象,這個過程耗時極短;以後併發標記時,標記線程將和用戶線程併發執行,這個過程耗時最長,但因爲和用戶線程併發執行因此這段時間並不會形成停頓;從新標記將對併發標記階段用戶進程形成的對象引用變動進行再次標記,這個階段也會中止全部用戶線程;併發清除階段,清除進程將和用戶進程併發進行;

優勢:極低的停頓時間,在注重交互的系統中,體驗極佳

缺點:

  1. 在併發階段,收集器線程將一直獨佔一部分CPU資源,默認是(CPU數量+3)/4的CPU核數,當CPU核數<=4時,佔用資源過多,將形成用戶進程執行速度下降;
  2. 在併發清除期間參數的浮動垃圾(Floating Garbage)沒法處理,只能下次GC處理,因此CMS收集器的激活頻率會高於其餘,默認當老年代使用了68%空間激活,而其餘同代收集器幾乎徹底填滿才進行收集,能夠經過參數-XX:CMSInitiatingOccupancyFraction設置激活所須要的內存佔用百分比;
  3. 因爲CMS使用「標記-清除」算法,將產生大量內存碎片,可能形成老年代內存剩餘足夠,但找不到合適大小的內存空間,不得不提早進行一次Full GC。爲了解決這個問題CMS收集器提供了-XX:+UseCMSCompactAtFullCollection,當出現上述狀況時,進行內存碎片合併,還有另外一個參數-XX:CMSFullGCsBeforeCompaction,默認爲0,即每次進入Full GC時都進行內存碎片整理

G1(Garbage first)收集器:

Sun賦予它的使命是將來能夠替代CMS,

G1優點:

  • 並行與併發
  • 分代收集
  • 空間整合
  • 可預測的停頓

G1的運做大體可分爲下面的幾個步驟:

  • 初始標記(Initial Marking)
  • 併發標記(Concurrent Marking)
  • 最終標記(Final Marking)
  • 篩選回收(Live Data Counting and Evacuation)

對象分配空間的規則:

大多數時候,新對象分配在Eden區,當Eden區內存不足時,系統將發起一次Minor GC,但也有特殊狀況:

  • 須要大量連續空間的Java對象,如長字符串,數組,須要內存超過-XX:PretenureSizeThreshold參數設置值,將直接在老年代分配

當對象在Eden出生每通過一次Minor GC並存活下來,年齡計數加1,當年齡大於-XX:MaxTensuringThreshold設置值,將進入老年代,也有特殊狀況:

  • 當出現Survivor區中相同年齡對象總大小大於Survivor區一半,年齡大於等於該年齡的對象都進老年代

在每次Minor GC 開始以前,虛擬機將進行如下流程:

*是否容許失敗,有參數-XX:+HandlePromotionFailure設置

相關文章
相關標籤/搜索