垃圾收集器與內存分配策略
前言:本文基於《深刻java虛擬機》再加上我的的理解以及其餘相關資料,對內容進行整理濃縮總結。本文中的圖來自網絡,感謝圖的做者。若是有不正確的地方,歡迎指出。html
目錄
回顧
- 上文介紹了jvm的內存區域以及介紹了內存的溢出狀況。
- jvm區域分爲5個,線程獨有:虛擬機棧,本地方法棧,程序計數器。線程共享:方法區,堆
- 兩種溢出:棧溢出(StackOverflowError),OutOfMemoryError(OOM)
爲何學習垃圾收集
- 看起來jvm好像一切幫你作好,可是當垃圾收集成爲系統達到更高併發量的瓶頸時,咱們就須要對這種自動化的技術進行監控和調節。
- 根據實際應用需求,選擇最優的收集方式才能更高的性能。
垃圾收集的區域
- 虛擬機棧,本地方法棧,程序計數器是線程私有的,和線程同生共死,當線程銷燬時,內存天然回收,因此這部分不是考慮的重點。
- 因此研究重點應該在方法區和堆,而方法區的回收效率較低,重點在堆。
肯定回收對象
引用計數
- 對象有一個引用計數器 new String()
- 有引用,計數器加一,String jiajun=new String()
- 引用失效,計數器減一,jiajun=null;
- 出現一個問題
Person jia=new Persoon();
Person jun=new Person();
jia.bro=jun;
jun.bro=jia;
jia=null;
jun=null;
- 此時雖然咱們沒有辦法用jia和jun這兩個對象,也就是這個是垃圾了,可是兩個對象又互相引用,若是用這種算法是沒法進行回收
可達性分析
- GC Roots的對象:虛擬機棧中引用的對象(好比方法中定義的對象),本地方法棧中引用的對象,方法區中類靜態屬性引用的對象(static),方法區中常量應用的對象(final)
- 經過GC Roots對象做爲起點,當GC Roots到一個對象沒有引用鏈的話,那麼就證實這個而對象不可用
垃圾收集算法
標記清除
- 肯定回收對象,進行標記,而後回收標記的對象
- 一個問題是標記清除效率不高
- 一個問題是標記清除後產生大量不連續的內存碎片,但須要分配較大對象的時候,由於沒有足夠大的連續空間分配給此對象,此時會觸發另外一次垃圾收集
複製
- 解決內存碎片的問題
- 將內存分爲等大的A區和B區,建立對象時,存放於A區,當A區空間用完以後,將A區存活的對象複製到B區,而後A區清空,這樣的話,就避免了內存碎片的問題
- 可是又有一個問題,這個時候咱們只用到了一半的空間,因此咱們要向辦法提升空間利用率
- 研究發現,新生代對象大多都是朝生夕死,也就是存活的對象很少,那麼咱們就不必分配一半的空間用於「粘貼」
- 因此虛擬機將新生代分爲Eden和Surivior兩個區,比例爲8:1:1,可用的內存爲一個伊甸區個一個存活區,一個存活區用於「粘貼,」,也就是新生代可用內存空間爲容量的90%,這樣的話就提升了空間了利用率
- 可是又有一個問題,若是這一個存活區存放不了複製的的對象,那麼怎麼辦?因而,若是這些對象放不下,將直接進入另外一塊區域老年代
標記整理
- 對於朝生夕死的新生代來講,複製算法是不錯的選擇。可是對於存活率高的對象不是很好的選擇,由於要進行較多的複製操做,效率會下降
- 過程:肯定回收對象,進行標記,讓存活的對象向一端移動,而後清理掉端邊界之外的內存
- 那麼這樣的話,不會有內存碎片的問題,也沒有複製過多效率下降的問題
分代收集
- 上面幾種方法的綜合利用
- 根據新生代和老年代的特色,分別選用適用的算法。
- 新生代存活率,那麼能夠選擇複製算法
- 老年代存活率高,能夠採用標記清理或標記整理算法
垃圾收集器
Stop the world(STW)
- 意思指GC時,停頓全部java執行線程
- 由於在進行可達性分析時候,若是同時對象引用進行變化,那麼這樣可達性分析就不正確。這是stop the world 的重要緣由
Serial收集器
- 用一條線程去完成垃圾收集工做
- 垃圾收集時,須要暫停其餘工做線程,顯然這是很差,因此縮短線程停頓的時間是一個研究重點
- 採用複製算法,用於新生代
Serial Old收集器
- 與Serial收集器相似
- 採用標記整理算法,用於老年代
ParNew收集器
- 用多條線程去完成收集工做
- 垃圾收集時,須要暫停其餘工做線程
- 默認開啓的收集線程數和cpu數量同樣,ParallelGCThreads參數能夠用來設置線程數
- 用於新生代,複製算法
Parallel Scavenge收集器
- 新生代收集器,使用複製算法
- 關注吞吐量,吞吐量=代碼運行時間/(代碼運行時間+垃圾收集時間),也就是高效率利用cpu時間,儘快完成程序的運算任務
- MaxGCPauseMillis參數設置最大停頓時間,GCTimeRatio設置吞吐量大小
Parllel Old收集器
- Parallel Scavenge的老年代版
- 使用標記整理算法
CMS收集器
- 用於老年代
- 關注停頓時間,但願獲取最短回收停頓時間
- 基於標記清除算法,會產生內存碎片
- 整體來講和用戶線程一塊兒併發執行
- 對cpu資源敏感,也就是會佔用較多cpu資源
- 沒法處理浮動垃圾,因爲是和用戶線程併發執行,因此併發時用戶線程產生的的新垃圾(浮動垃圾)沒法收集。
G1收集器
- 使用多個cpu來縮短STW(stop the world )的停頓時間
- 分代收集
- 不會產生內存空間碎片
- 能夠創建可預測的停頓時間模型,可以讓使用者指定M毫秒時間段內,垃圾收集的時間不超過N毫秒
流行的收集器組合
Serial |
Serial Old |
Serial |
CMS |
ParNew |
CMS |
ParNew |
Serial Old |
Parallel Scavenge |
Serial Old |
Parallel Scavenge |
Parallel Old |
G1 |
G1 |
垃圾收集參數總結
UseSerialGC |
虛擬機運行在Client 模式下的默認值,打開此開關後,使用Serial +Serial Old 的收集器組合進行內存回收 |
UseParNewGC |
打開此開關後,使用ParNew + Serial Old 的收集器組合進行內存回收 |
UseConcMarkSweepGC |
打開此開關後,使用ParNew + CMS + Serial Old 的收集器組合進行內存回收。Serial Old 收集器將做爲CMS 收集器出現Concurrent Mode Failure失敗後的後備收集器使用 |
UseParallelGC |
虛擬機運行在Server 模式下的默認值,打開此開關後,使用ParallelScavenge + Serial Old(PS MarkSweep)的收集器組合進行內存回收 |
UseParallelOldGC |
打開此開關後,使用Parallel Scavenge + Parallel Old 的收集器組合進行內存回收 |
SurvivorRatio |
新生代中Eden 區域與Survivor 區域的容量比值, 默認爲8, 表明Eden :Survivor=8∶1 |
PretenureSizeThreshold |
直接晉升到老年代的對象大小,設置這個參數後,大於這個參數的對象將直接在老年代分配 |
MaxTenuringThreshold |
晉升到老年代的對象年齡。每一個對象在堅持過一次Minor GC 以後,年齡就加1,當超過這個參數值時就進入老年代 |
UseAdaptiveSizePolicy |
動態調整Java 堆中各個區域的大小以及進入老年代的年齡 |
HandlePromotionFailure |
是否容許分配擔保失敗,即老年代的剩餘空間不足以應付新生代的整個Eden 和Survivor 區的全部對象都存活的極端狀況 |
ParallelGCThreads |
設置並行GC 時進行內存回收的線程數 |
GCTimeRatio |
GC 時間佔總時間的比率,默認值爲99,即容許1% 的GC 時間。僅在使用Parallel Scavenge 收集器時生效 |
MaxGCPauseMillis |
設置GC 的最大停頓時間。僅在使用Parallel Scavenge 收集器時生效 |
CMSInitiatingOccupancyFraction |
設置CMS 收集器在老年代空間被使用多少後觸發垃圾收集。默認值爲68%,僅在使用CMS 收集器時生效 |
UseCMSCompactAtFullCollection |
設置CMS 收集器在完成垃圾收集後是否要進行一次內存碎片整理。僅在使用CMS 收集器時生效 |
CMSFullGCsBeforeCompaction |
設置CMS 收集器在進行若干次垃圾收集後再啓動一次內存碎片整理。僅在使用CMS 收集器時生效 |
內存分配與回收策略
GC種類
- Minor GC,新生代GC,回收速度快
- Major GC/ Full GC , 收集整個堆,包括yong gen,old gen,perm gen,回收速度慢
對象優先分配在新生代中的Eden
- 大多數狀況下,對象分配在Eden區,若是Eden區空間不夠,將發起一次MinorGC
大對象直接進入老年代
- 大對象指須要大量連續內存空間的對象
- 常常出現大對象,因爲須要連續的空間,容易致使內存還有很多空間就提早觸發垃圾收集
- 經過PretenureSizeThreshold參數設置限制,超過這個限制的對象直接分配在老年代
長時間存活的對象將進入老年代
- 每一個對象有一個對象年齡計數器,通過第一次MinorGC進入存活區,年齡爲一,之後通過MinorGC還能繼續在存活區的話,年齡加一,當經過限制(MaxTenuringThreshold)後會晉升到老年代
動態對象年齡斷定
- 存活區空間相同年齡全部對象大小的總和大於存活區的空間一半,大於等於該年齡的能夠直接進入老年代
空間分配擔保
- Minor GC以前,虛擬機檢查老年代最大可用的連續空間是否大於新生代全部對象總空間,若是能夠,能夠安全進行
- 若是不行,看是否容許擔保失敗,不容許的話進行Full GC
- 容許的話,看老年代的最大可用連續空間是否大於歷次晉升老年代對象的平均大小(將以前的做爲經驗值),若是大於,進行MinorGC,不然進行FullGC
方法區回收
回收內容
廢棄常量
- 沒有任何地方引用常量池的字符串常量,必要的話,這個字符串會被清除常量池。常量池中的類 方法 字段的符號引用也是相似
無用的類
- 不存在該類的任何對象
- 加載該類的ClassLoader已經被回收
- 該類的對應的Class對象沒有被引用,沒法在任何地方經過反射訪問該類的方法
gc觸發條件
ygc
full gc
- 建立大對象時,eden區空間不足,會嘗試分配到老年代,若是老年代空間也不足就會觸發full gc
- 觸發yong gc的時候,會檢查老年代的最大連續空間是否大於新生代全部對象總空間,若是大於的話,說明此次yong gc是安全的。若是小於的話,看是否容許擔保,不容許擔保的話,那麼進行full gc。若是容許擔保的話,那麼參考一下歷次晉升到老年代的對象的平均大小,若是老年代連續空間大於這個參考值,那麼嘗試進行ygc。若是小於這個參考值的話,沒辦法只能進行full gc。
- 當系統要加載的類,反射的類和調用的方法較多,而且永久代空間不足的話,進行full gc
- 當to區的空間不足的時候,會把對象複製到老年代,若是老年代空間不足會進行full gc
我以爲分享是一種精神,分享是個人樂趣所在,不是說我以爲我講得必定是對的,我講得可能不少是不對的,可是我但願我講的東西是我人生的體驗和思考,是給不少人反思,也許給你一秒鐘、半秒鐘,哪怕說一句話有點道理,引起本身心裏的感觸,這就是我最大的價值。(這是我喜歡的一句話,也是我寫博客的初衷)
做者:jiajun 出處: http://www.cnblogs.com/-new/
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。若是以爲還有幫助的話,能夠點一下右下角的【推薦】,但願可以持續的爲你們帶來好的技術文章!想跟我一塊兒進步麼?那就【關注】我吧。java