java 垃圾回收 新生代_Java垃圾回收
一、概述
Java垃圾回收器實現內存的自動分配和回收,這兩個操作都發生在Java堆上(還包括方法區,即永久代)。垃圾回收操作不是實時的發生(對象死亡不會立即釋放),當內存消耗完或者是達到某一指標(threshold,使用內存占總內存的比列,比如0.75)時,觸發垃圾回收操作。有一個對象死亡的例外,java.lang.Thread類型的對象即使沒有引用,只要線程還在運行,就不會被回收。major collection會引發minor collection。
JVM中,程序計數器、虛擬機棧、本地方法棧有與線程是相同的生命周期。棧幀隨著方法的進入和退出做入棧和出棧操作,從而實現內存的自動清理。因此,內存垃圾回收主要集中于堆和方法區。
對象存活判斷方式
引用計數:每個對象有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數為0時可以回收。此方法簡單,但無法解決對象相互循環引用的問題。
可達性分析:從GC Roots開始向下搜索,搜索所走過的路徑稱為引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。即為不可達對象。
在Java語言中,GC Roots包括:
虛擬機棧中引用的對象。
方法區中類靜態屬性引用的對象。
方法區中常量引用的對象。
本地方法棧中JNI引用的對象。
二、回收的算法
引用計數(reference counting)
比較古老的回收算法。原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,只用收集計數為0的對象。此算法最致命的是無法處理循環引用問題。
標記-清除(mark-sweep)
此算法執行分兩階段。第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,而且會產生內存碎片。
復制(copying)
此算法把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象復制到另外一個區域中。此算法每次只處理 正在使用中的對象,因此復制成本比較小,同時復制過去以后還能進行相應的內存整理,不會出現“碎片”問題。此算法的缺點,就是需要兩倍內存空間。
標記-整理(mark-compact)
此算法結合了“標記-清除”和“復制”兩個算法的優點。是分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,清除未標記對象并且把存活對象“壓縮”到堆的其中一塊,按順序排放。此算法避免了“標記-清除”的碎片問題,同時也避免了“復制”算法的空間問題。
分代(generational collecting)
基于對對象生命周期分析后得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命周期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。
分區(G1)
三、回收的機制
依統計分析,大多數對象生命周期都是短暫的,所以把Java內存分代管理。
四、回收的性能指標(Performance Metrics)
吞吐量(Throughput) 在一個較長的周期內,非回收時間占總時間的比率。
暫停時間(Pause time) Java虛擬機在回收垃圾的時候,有的算法會暫停所有應用線程的執行。
五、回收的區域
年輕代(Young Generation)
年輕代被劃分為三個區域,伊甸區(eden)和兩個小的幸存區(survivor),兩個存活區按功能分為From和To。絕大多數的對象都在eden區分配,超過一個垃圾回收操作仍然存活的對象放到幸存區。當Eden區滿時,還存活的對象將被復制到survivor區(兩個中的一個),當這個survivor區滿時,此區的存活對象將被復制到另外一個survivor區,當這個survivor區也滿了的時候,從第一個Survivor區復制過來的并且此時還存活的對象,將被復制“年老區(tenured)”。需要注意,survivor的兩個區是對稱的,沒先后關系,survivor區總有一個是空的。
年老代(Old Generation)
主要存儲年輕代中經過多個回收周期仍然存活從而升級的對象,對于一些大的內存分配,可以直接分配到永久代。
永久代(Permanent Generation)
存儲類、方法以及它們的描述信息。通常不需要調節該參數,但是有些應用可能動態生成或者調用一些class, 這時需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。
六、回收的類型
minor GC
當新對象生成,在eden申請空間失敗時,就會觸發minor GC,對eden區域進行GC,清除非存活對象,并且把尚且存活的對象移動到survivor區。然后整理survivor的兩個區。
full GC
對整個堆進行整理,包括Young、Tenured和Perm。有如下原因可能導致Full GC:
- Tenured被寫滿
- Perm域被寫滿
- System.gc()被顯示調用
- 上一次GC之后heap的各域分配策略動態變化
七、垃圾回收器類型
串行收集器: 使用單線程處理所有垃圾回收工作,因為無需多線程交互,所以效率比較高。但是,也無法使用多處理器的優勢,所以此收集器適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。使用-XX:+UseSerialGC打開。
并行收集器: 對年輕代進行并行垃圾回收,因此可以減少垃圾回收時間。一般在多線程多處理器機器上使用。使用-XX:+UseParallelGC打開。并行收集器在J2SE5.0第6更新上引入,在Java SE6.0中進行了增強--可以對年老代進行并行收集。如果年老代不使用并發收集的話,是使用單線程進行垃圾回收,因此會制約擴展能力。使用-XX:+UseParallelOldGC打開。
使用-XX:ParallelGCThreads設置并行垃圾回收的線程數。此值可以設置與機器處理器數量相等。
此收集器可以進行如下配置:
§ 最大垃圾回收暫停: 指定垃圾回收時的最長暫停時間,通過-XX:MaxGCPauseMillis指定。為毫秒。如果指定了此值的話,堆大小和垃圾回收相關參數會進行調整以達到指定值。設定此值可能會減少應用的吞吐量。
§ 吞吐量:吞吐量為垃圾回收時間與非垃圾回收時間的比值,通過-XX:GCTimeRatio=來設定,公式為1/(1+N)。例如,-XX:GCTimeRatio=19時,表示5%的時間用于垃圾回收。默認情況為99,即1%的時間用于垃圾回收。
并發收集器:可以保證大部分工作都并發進行(應用不停止),垃圾回收只暫停很少的時間,此收集器適合對響應時間要求比較高的中、大規模應用。使用-XX:+UseConcMarkSweepGC打開。
1.主要減少年老代的暫停時間,在應用不停止的情況下使用獨立的垃圾回收線程,跟蹤可達對象。在每個年老代垃圾回收周期中,在收集初期并發收集器會對整個應用進行簡短的暫停,在收集中還會再暫停一次。第二次暫停會比第一次稍長,在此過程中多個線程同時進行垃圾回收工作。
2. 并發收集器使用多處理器換來短暫的停頓時間。在一個N個處理器的系統上,并發收集部分使用K/N個可用處理器進行回收,一般情況下1<=K<=N/4。
3. 在只有一個處理器的主機上使用并發收集器,設置為incremental mode模式也可獲得較短的停頓時間。
4. 浮動垃圾:由于在應用運行的同時進行垃圾回收,所以會有些垃圾在垃圾回收完成時產生,這樣就造成了“Floating Garbage”,這些垃圾需要在下次垃圾回收周期時才能回收掉。所以,并發收集器一般需要20%的預留空間用于這些浮動垃圾。
5. Concurrent Mode Failure:并發收集器在應用運行時進行收集,所以需要保證堆在垃圾回收的這段時間有足夠的空間供程序使用,否則,垃圾回收還未完成,堆空間先滿了。這種情況下將會發生“并發模式失敗”,此時整個應用將會暫停,進行垃圾回收。
6. 啟動并發收集器:因為并發收集在應用運行時進行收集,所以必須保證收集完成之前有足夠的內存空間供程序使用,否則會出現“Concurrent Mode Failure”。通過設置-XX:CMSInitiatingOccupancyFraction指定還有多少剩余堆時開始執行并發收集。
八、具體的垃圾回收器
新生代
1 serial收集器
serial收集器是Hotspot運行在client模式下的默認新生代收集器, 它的特點是只用一個CPU/一條收集線程去完成GC工作, 且在進行垃圾收集時必須暫停其他所有的工作線程(“Stop The World” -后面簡稱STW)。
gc.serial.png
雖然是單線程收集, 但簡單而高效, 在VM管理內存不大的情況下(收集幾十M~一兩百M的新生代), 停頓時間完全可以控制在幾十毫秒~一百多毫秒內.
2 ParNew收集器
ParNew收集器是前面serial的多線程版本, 除使用多個線程進行GC外, 包括Serial可用的所有控制參數、收集算法、STW、對象分配規則、回收策略等都與serial完全一樣(也是VM啟用CMS收集器-XX: +UseConcMarkSweepGC的默認新生代收集器).
gc.parnew.png
由于存在線程切換的開銷, ParNew在單CPU的環境中比不上Serial, 且在通過超線程技術實現的兩個CPU的環境中也不能100%保證能超越Serial. 但隨著可用的CPU數量的增加, 收集效率肯定也會大大增加(ParNew收集線程數與CPU的數量相同, 因此在CPU數量過大的環境中, 可用-XX:ParallelGCThreads參數控制GC線程數).
3 Parallel Scavenge收集器
與ParNew類似,Parallel Scavenge也是使用復制算法,也是并行多線程收集器。 但與其他收集器關注盡可能縮短垃圾收集時間不同, Parallel Scavenge更關注系統吞吐量。
系統吞吐量=運行用戶代碼時間 /(運行用戶代碼時間+垃圾收集時間)
停頓時間越短就越適用于用戶交互的程序--良好的響應速度能提升用戶的體驗;而高吞吐量則適用于后臺運算而不需要太多交互的任務--可以最高效率地利用CPU時間,盡快地完成程序的運算任務。
老年代
1 Serial Old收集器
Serial old是serial收集器的老年代版本, 同樣是單線程收集器,使用“標記-整理”算法。
gc.serial.old.png
2 Parallel Old收集器
Parallel old是Parallel Scavenge收老年代版本, 使用多線程和“標記-整理”算法。 吞吐量優先,主要與Parallel Scavenge配合在注重吞吐量 及 CPU資源敏感系統內使用。
gc.parnew.old.png
3 CMS收集器
CMS(Concurrent Mark Sweep)收集器是真正意義上的并發收集器,雖然已經有了理論上表現更好的G1收集器。
CMS以獲取最短回收停頓時間為目標的收集器,基于”標記-清除”算法實現,整個GC過程分為以下4個步驟:
初始標記
并發標記(GC Roots Tracing過程)
重新標記
并發清除(CMS concurrent sweep: 已死象會就地釋放,注意: 此處沒有壓縮)
其中初始標記和重新標記仍需STW。但初始標記僅只標記一下GC Roots能直接關聯到的對象, 速度很快。重新標記是為了修正并發標記期間因用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄,雖然一般比初始標記階段稍長,但要遠小于并發標記時間。
gc.cms.png
(由于整個GC過程耗時最長的并發標記和并發清除階段的GC線程可與用戶線程一起工作, 所以總體上CMS的GC過程是與用戶線程一起并發地執行的。
由于CMS收集器將整個GC過程進行了更細粒度的劃分, 因此可以實現并發收集、低停頓的優勢,但它也并非十分完美,其存在缺點及解決策略如下:
CMS默認啟動的回收線程數=(CPU數目+3)4
當CPU數>4時,GC線程最多占用不超過25%的CPU資源,但是當CPU數<=4時, GC線程可能就會過多的占用用戶CPU資源, 從而導致應用程序變慢, 總吞吐量降低。
無法處理浮動垃圾, 可能出現Promotion Failure、Concurrent Mode Failure而導致另一次Full GC的產生。
浮動垃圾是指在CMS并發清理階段用戶線程運行而產生的新垃圾。由于在GC階段用戶線程還需運行, 因此還需要預留足夠的內存空間給用戶線程使用,導致CMS不能像其他收集器那樣等到老年代幾乎填滿了再進行收集。 因此CMS提供了-XX:CMSInitiatingOccupancyFraction參數來設置GC的觸發百分比(以及-XX:+UseCMSInitiatingOccupancyOnly來啟用該觸發百分比),當老年代的使用空間超過該比例后CMS就會被觸發(JDK 1.6之后默認92%)。 但當CMS運行期間預留的內存無法滿足程序需要,就會出現Promotion Failure等失敗, 這時VM將啟動后備預案:臨時啟用serial old收集器來重新執行full GC(CMS通常配合大內存使用,一旦大內存轉入串行的serial GC, 那停頓的時間就是大家都不愿看到的了)。
最后,由于CMS采用”標記-清除”算法實現, 可能會產生大量內存碎片。內存碎片過多可能會導致無法分配大對象而提前觸發Full GC。因此CMS提供了-XX:+UseCMSCompactAtFullCollection參數,用于在Full GC后再執行一個碎片整理過程。 但內存整理是無法并發的,內存碎片問題雖然沒有了,但停頓時間也因此變長了,因此CMS還提供了另外一個參數-XX:CMSFullGCsBeforeCompaction用于設置在執行N次不進行內存整理的Full GC后,跟著來一次帶整理的(默認為0: 每次進入Full GC時都進行碎片整理)。
4 G1收集器
HotSpot開發團隊賦予它的使命是未來可以替換掉JDK1.5中發布的CMS收集器。與CMS收集器相比G1收集器有以下特點:
空間整合,G1收集器采用標記-整理算法,不產生內存空間碎片。分配大對象時不會因為無法找到連續空間而提前觸發下一次GC。
可預測停頓,降低停頓時間是G1和CMS的共同關注點,但G1除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為N毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒,這幾乎已經是實時Java(RTSJ)的垃圾收集器的特征了。
G1將Java堆劃分為多個大小相等的獨立區域(region),雖然還保留有新生代和老年代的概念, 但新生代和老年代不再是物理隔離的了, 它們都是一部分region(不需要連續)的集合。
gc.g1.png
每塊區域既有可能屬于O區、也有可能是Y區, 因此不需要一次就對整個老年代/新生代回收。而是當線程并發尋找可回收的對象時, 有些區塊包含可回收的對象要比其他區塊多很多。 雖然在清理這些區塊時G1仍然需要暫停應用線程, 但可以用相對較少的時間優先回收垃圾較多的regiorn(這也是G1命名的來源)。 這種方式保證了G1可以在有限的時間內獲取盡可能高的收集效率。
新生代收集
gc.g1.young01.png
G1的新生代收集跟ParNew類似: 存活的對象被轉移到一個/多個Survivor Regions. 如果存活時間達到閥值, 這部分對象就會被提升到老年代。
G1的新生代收集特點如下:
一整塊堆內存被分為多個Regions。
存活對象被拷貝到新的Survivor區或老年代。
年輕代內存由一組不連續的heap區組成,這種方法使得可以動態調整各代區域尺寸。
Young GCs會有STW事件, 進行時所有應用程序線程都會被暫停,
多線程并發GC。
老年代收集
G1老年代GC會執行以下階段:
注: 以下有些階段也是年輕代垃圾收集的一部分。
index
Phase
Description
(1)
初始標記 (Initial Mark: Stop the World )
在G1中, 該操作附著一次年輕代GC, 以標記Survivor中有可能引用到老年代對象的Regions。
(2)
掃描根區域 (Root Region Scanning: 與應用程序并發執行)
掃描Survivor中能夠引用到老年代的references. 但必須在Minor GC觸發前執行完。
(3)
并發標記 (Concurrent Marking : 與應用程序并發執行)
在整個堆中查找存活對象, 但該階段可能會被Minor GC中斷。
(4)
重新標記 (Remark : Stop the World )
完成堆內存中存活對象的標記. 使用snapshot-at-the-beginning(SATB, 起始快照)算法, 比CMS所用算法要快得多(空region直接被移除并回收,并計算所有區域的活躍度)。
(5)
清理 (Cleanup : Stop the World and Concurrent)
見下 5-1、2、3
5-1 (Stop the world)
在含有存活對象和完全空閑的區域上進行統計。
5-2 (Stop the world)
擦除Remembered Sets。
5-3 (Concurrent)
重置空regions并將他們返還給空閑列表(free list)。
(*)
Copying/Cleanup (Stop the World )
選擇”活躍度”最低的區域(這些區域可以最快的完成回收)。拷貝/轉移存活的對象到新的尚未使用的regions。 該階段會被記錄到gc-log(只發生年輕代[GC pause (young)], 與老年代一起執行則被記錄為[GC Pause (mixed)]。
G1老年代GC特點如下:
并發標記階段(index 3)
在與應用程序并發執行的過程中會計算活躍度信息。
這些活躍度信息標識出那些regions最適合在STW期間回收(which regions will be best to reclaim during an evacuation pause)。
不像CMS有清理階段。
再次標記階段(index 4)
使用Snapshot-at-the-Beginning(SATB)算法比CMS快得多。
空region直接被回收。
拷貝/清理階段(Copying/Cleanup Phase)
年輕代與老年代同時回收。
老年代內存回收會基于他的活躍度信息。
補充: 關于Remembered Set
G1收集器中, region之間的對象引用以及其他收集器中的新生代和老年代之間的對象引用都是使用remembered set來避免掃描全堆。G1中每個Region都有一個與之對應的Remembered Set,VM發現程序對Reference類型數據進行寫操作時,會產生一個Write Barrier暫時中斷寫操作,檢查Reference引用的對象是否處于不同的Region中(在分代例子中就是檢查是否老年代中的對象引用了新生代的對象), 如果是, 便通過CardTable把相關引用信息記錄到被引用對象所屬的Region的Remembered Set中。當內存回收時,在GC根節點的枚舉范圍加入Remembered Set即可保證不對全局堆掃描也不會有遺漏。
常用的收集器組合
gc.allCollctor.jpeg
建議:如果系統花費很多的時間收集垃圾,請減小堆大小。full gc不應該超過 3-5 秒.
thread不要設的太高,一般一個cpu200個,多了的話,線程切換反而導致性能下降。
參考
總結
以上是生活随笔為你收集整理的java 垃圾回收 新生代_Java垃圾回收的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python围棋程序在屏幕上找棋盘_用C
- 下一篇: 同一款产品内墙第一遍刮耐水腻子二遍刮普通