哪些內存要回收?html
方法區和堆的內存只有在程序運行期間才能直到會建立哪些對象,這部份內存分配和回收都是動態的。這部份內存是垃圾收集器所關注的。
java
程序計數器,虛擬機棧,本地方法棧是線程隔離的,當方法結束或線程結束時,內存就回收了。
算法
何時回收?安全
(1)可達性分析算法中不可達的對象(第一次標記)
數據結構
(2)篩選出有必要執行finalize()方法的對象(此對象沒有覆蓋finalize()方法,或者finalize()方法已被虛擬機調用過則視爲不必執行)
多線程
(3)放置在F-Queue隊列中併發
如何回收?ide
垃圾收集器
性能
一. 判斷對象須要回收spa
1.可達性分析算法:
當一個對象到GC Root沒有任何引用鏈相連時,則證實此對象是不可用的。
2.引用
強引用>弱引用>軟引用>虛引用
3.是否肯定死亡
4.回收方法區
回收兩部份內容:廢棄常量和無用的類。
廢棄常量:沒有任何地方引用這個字面量,若是這時發生內存回收且有必要的話,該常量會被系統清理出常量池。
無用的類:
-- 該類全部的實例都已經被回收,java堆中不存在該類的任何實例;
-- 加載該類的ClassLoader已經被回收;
--該類對應的java.lang.Class對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該類的方法。
二. 垃圾收集算法
1.標記-清除算法
標記出全部須要回收的對象,標記完後贊成回收被標記的對象。
2.複製算法(新生代)
將可用內存兩等分,當一塊用完後,將還存活的對象複製到另外一塊中,把已使用的一塊清除。
對象存活率較高時要進行較多複製操做,效率低,還須要有額外的空間進行分配擔保,故不適合老年代。
3.標記-整理算法(老年代)
標記出全部須要回收的對象,讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。
三. HotSpot算法實現
1.枚舉根節點:
在進行可達性分析時必須停頓全部Java執行線程。
在HotSpot中使用一組OopMap的數據結構來得知哪些地方存放着對象引用。
在類加載完成時,HotSpot就把對象內什麼偏移量上是什麼類型的數據計算出來,在JIT編譯過程當中也會在特定的位置記錄下棧和寄存器中哪些位置是引用。
在OopMap協助下,HotSpot能夠快速且準確地完成GC Root枚舉。
2.安全點(SafePoint):
程序執行時只有在到達安全點才能暫停,開始GC。
安全點的選定:程序」是否具備讓程序長時間執行的特徵「爲標準進行選定。
-- 由於每條指令執行的時間都很是短,程序不太可能由於指令流長度太長這個緣由而過長時間運行,「長時間執行」的最明顯特性就是指令序列複用,例如方法調用、循環跳轉、異常跳轉等。
如何在發生GC時讓全部線程都跑到最近的安全點上再停頓下來:
-- 搶先式中斷(不採用):不須要線程執行代碼主動去配合。在GC發生時,首先把全部線程所有中斷,若是發現有線程中斷的地方不在安全點上,就回複線程,讓它跑到安全點上。
-- 主動式中斷:當GC須要中斷線程時,不直接對線程操做,設置一個標誌,各線程執行時主動去輪詢這個標誌,發現中斷標誌位真時就本身中斷掛起。
輪詢標誌的地方和安全點是重合的。
Safepoint機制保證了程序執行時,在不太長的時間內就會遇到可進入GC的Safepoint。
3.安全區域(Safe Region):
在線程處於sleep狀態或Blocked狀態,程序沒法響應JVM的中斷請求,走到安全的地方去中斷掛起,這種狀況須要安全區域來解決。
安全區域:在一段代碼片斷中,引用關係不會發生變化。在這個區域的任意地方開始GC都是安全的。Safe Region能夠當作是被擴展的Safepoint。
當線程執行到Safe Region中的代碼,首先標識本身已經進入Safe Region,當在這點時間裏JVM發起GC時,就不用管標識本身爲Safe Region狀態的線程了。
在線程要離開Safe Region時要檢查系統是否已經完成了根節點枚舉(或是整個GC過程),若是完成線程就繼續執行,不然它必須等待直到收到能夠安全離開Safe Region的信號爲止。
四. 垃圾收集器
垃圾收集器是內存回收的具體實現。
圖爲HotSpot虛擬機的垃圾收集器:(存在連線說明他們之間能搭配使用)
並行和併發在垃圾收集器中的解釋:
並行(Parallel):指多條垃圾收集線程並行工做,但此時用戶線程仍然處於等待狀態;
併發(Concurrent):指用戶線程與垃圾收集器線程同時執行(單不必定是並行的,可能會交替執行),用戶程序在繼續運行,而垃圾收集程序運行於另外一個CPU上。
1.Serial(串行GC)收集器
Serial收集器是一個新生代收集器,單線程執行,使用複製算法。它在進行垃圾收集時,它不只只會使用一個CPU或者一條收集線程去完成垃圾收集做,並且必須暫停其餘全部的工做線程(用戶線程),直到它收集完成。
Serial/Serial Old收集器運行示意圖(表示Serial和Serial Old搭配使用)
是Jvm client模式下默認的新生代收集器。對於限定單個CPU的環境來講,簡單高效,Serial收集器因爲沒有線程交互的開銷,專心作垃圾收集天然能夠得到最高的單線程收集效率,所以是運行在Client模式下的虛擬機的不錯選擇(好比桌面應用場景)。
2.ParNew(並行GC)收集器
ParNew收集器其實就是serial收集器的多線程版本,使用複製算法。除了使用多條線程進行垃圾收集以外,其他行爲與Serial收集器同樣。
ParNew/Serial Old收集器運行示意圖(表示ParNew和Serial Old搭配使用)
是運行在Service模式下虛擬機中首選的新生代收集器,其中一個與性能無關的緣由就是除了Serial收集器外,目前只有ParNew收集器能與CMS收集器配合工做。
PreNew收集器在單CPU環境中絕對沒有Serial的效果好,因爲存在線程交互的開銷,該收集器在超線程技術實現的雙CPU中都不能必定超過Serial收集器。默認開啓的垃圾收集器線程數就是CPU數量,可經過-XX:parallelGCThreads參數來限制收集器線程數。
PreNew收集器是使用-XX:+UseConcMarkSweepGC選項後的默認新生代收集器,也可使用-XX:+UseParNewGC選項來強制指定它。
3.Parallel Scavenge(吞吐量優先)收集器
Parallel Scavenge收集器也是一個新生代收集器,它也是使用複製算法的收集器,又是並行多線程收集器。parallel Scavenge收集器的特色是它的關注點與其餘收集器不一樣,CMS等收集器的關注點是儘量地縮短垃圾收集時用戶線程的停頓時間,而parallel Scavenge收集器的目標則是達到一個可控制的吞吐量。吞吐量= 程序運行時間/(程序運行時間 + 垃圾收集時間),虛擬機總共運行了100分鐘。其中垃圾收集花掉1分鐘,那吞吐量就是99%。
Parallel Scavenge/Parallel Old收集器運行示意圖(表示Parallel Scavenge和Parallel Old搭配使用)
短停頓時間適合和用戶交互的程序,體驗好。高吞吐量適合高效利用CPU,主要用於後臺運算不須要太多交互。
提供了兩個參數來精確控制吞吐量:1.最大垃圾收集器停頓時間(-XX:MaxGCPauseMillis 大於0的毫秒數,停頓時間小了就要犧牲相應的吞吐量和新生代空間),2.設置吞吐量大小(-XX:GCTimeRatio 大於0小於100的整數,默認99,也就是容許最大1%的垃圾回收時間)。
還有一個參數表示自適應調節策略(GC Ergonomics)(-XX:UseAdaptiveSizePolicy)。這是一個開關參數,當這個參數打開以後,就不用手動設置新生代大小(-Xmn)、Eden和Survivor區的比例(-XX:SurvivorRatio)此生老年代對象大小(-XX:PretenureSizeThreshold),會根據當前系統的運行狀況手機監控信息,動態調整停頓時間和吞吐量大小。也是其與PreNew收集器的一個重要區別,也是其沒法與CMS收集器搭配使用的緣由(CMS收集器儘量地縮短垃圾收集時用戶線程的停頓時間,以提高交互體驗)。
4.Serial Old(MSC)收集器
Serial Old是Serial收集器的老年代版本,它一樣使用一個單線程執行收集,使用「標記-整理」算法。主要使用在Client模式下的虛擬機。
Serial/Serial Old收集器運行示意圖(表示Serial和Serial Old搭配使用)
若是在Service模式下使用:1.一種是在JDK1.5以及以前的版本中與Parallel Scavenge收集器搭配使用,由於那時尚未Parallel Old老年代收集器搭配;2.另外一種就是做爲CMS收集器的後備預案,在併發收集發生Concurrent Model Failure時使用。
5.Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和「標記-整理」算法,JDK1.6才提供。
因爲以前有一個Parallel Scavenge新生代收集器,,可是卻無老年代收集器與之完美結合,只能採用Serial Old老年代收集器,可是因爲Serial Old收集器在服務端應用性能上低下(畢竟單線程,多CPU浪費了),其吞吐量反而不必定有PreNew+CMS組合。
Parallel Scavenge/Parallel Old收集器運行示意圖(表示Parallel Scavenge和Parallel Old搭配使用)
6.CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。CMS收集器是HotSpot虛擬機中的一款真正意義上的併發收集器,第一次實現了讓垃圾回收線程和用戶線程(基本上)同時工做。用CMS收集老年代的時候,新生代只能選擇Serial或者ParNew收集器。
CMS收集器是基於「標記-清除」算法實現的,整個收集過程大體分爲4個步驟:
①.初始標記(CMS initial mark)
②.併發標記(CMS concurrenr mark)
③.從新標記(CMS remark)
④.併發清除(CMS concurrent sweep)
其中初始標記、從新標記這兩個步驟任然須要停頓其餘用戶線程(Stop The World)。初始標記僅僅只是標記出GC ROOTS能直接關聯到的對象,速度很快,併發標記階段是進行GC ROOTS 根搜索算法階段,會斷定對象是否存活。而從新標記階段則是爲了修正併發標記期間,因用戶程序繼續運行而致使標記產生變更的那一部分對象的標記記錄,這個階段的停頓時間會被初始標記階段稍長,但比並發標記階段要短。
因爲整個過程當中耗時最長的併發標記和併發清除過程當中,收集器線程均可以與用戶線程一塊兒工做,因此總體來講,CMS收集器的內存回收過程是與用戶線程一塊兒併發執行的。
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。CMS收集器是HotSpot虛擬機中的一款真正意義上的併發收集器,第一次實現了讓垃圾回收線程和用戶線程(基本上)同時工做。用CMS收集老年代的時候,新生代只能選擇Serial或者ParNew收集器。
CMS收集器是基於「標記-清除」算法實現的,整個收集過程大體分爲4個步驟:
①.初始標記(CMS initial mark)
②.併發標記(CMS concurrenr mark)
③.從新標記(CMS remark)
④.併發清除(CMS concurrent sweep)
其中初始標記、從新標記這兩個步驟任然須要停頓其餘用戶線程(Stop The World)。初始標記僅僅只是標記出GC ROOTS能直接關聯到的對象,速度很快,併發標記階段是進行GC ROOTS 根搜索算法階段,會斷定對象是否存活。而從新標記階段則是爲了修正併發標記期間,因用戶程序繼續運行而致使標記產生變更的那一部分對象的標記記錄,這個階段的停頓時間會被初始標記階段稍長,但比並發標記階段要短。
因爲整個過程當中耗時最長的併發標記和併發清除過程當中,收集器線程均可以與用戶線程一塊兒工做,因此總體來講,CMS收集器的內存回收過程是與用戶線程一塊兒併發執行的。
CMS收集器運行示意圖
CMS收集器的優勢:併發收集、低停頓,可是CMS還遠遠達不到完美,器主要有三個顯著缺點:
1.CMS收集器對CPU資源很是敏感。在併發(併發標記、併發清除)階段,雖然不會致使用戶線程停頓,可是會佔用CPU資源而致使應用程序變慢,總吞吐量降低。CMS默認啓動的回收線程數是:(CPU數量+3) / 4。收集器線程所佔用的CPU數量爲:(CPU+3)/4=0.25+3/(4*CPU)。所以這時垃圾收集器始終不會佔用少於25%的CPU,所以當進行併發階段時,雖然用戶線程能夠跑,可是很緩慢,特別是雙核CPU的時候,已經佔用了5/8的CPU,吞吐量會很低。爲了解決這種狀況,產生了「增量式併發收集器」(Incremental Concurrent Mark Sweep/i-CMS)。就是採用搶佔方式來模擬多任務機制,就是在併發(併發標記、併發清除)階段,讓GC線程、用戶線程交替執行,儘可能減小GC線程獨佔CPU,這樣垃圾收集過程更長,可是對用戶程序影響小一些。實際上i-CMS效果很通常,目前已經被聲明爲「deprecated」。
2.CMS收集器沒法處理浮動垃圾,可能出現「Concurrent Mode Failure「,失敗後而致使另外一次Full GC的產生。因爲CMS併發清理階段用戶線程還在運行,伴隨程序的運行自熱會有新的垃圾不斷產生,這一部分垃圾出如今標記過程以後,CMS沒法在本次收集中處理它們,只好留待下一次GC時將其清理掉。這一部分垃圾稱爲「浮動垃圾」。也是因爲在垃圾收集階段用戶線程還須要運行,即須要預留足夠的內存空間給用戶線程使用,所以CMS收集器不能像其餘收集器那樣等到老年代幾乎徹底被填滿了再進行收集,須要預留一部份內存空間提供併發收集時的程序運做使用。在默認設置下,CMS收集器在老年代使用了68%的空間時就會被激活,也能夠經過參數-XX:CMSInitiatingOccupancyFraction的值來提升觸發百分比,以下降內存回收次數提升性能。JDK1.6中,CMS收集器的啓動閾值已經提高到92%。要是CMS運行期間預留的內存沒法知足程序其餘線程須要,就會出現「Concurrent Mode Failure」失敗,這時候虛擬機將啓動後備預案:臨時啓用Serial Old收集器來從新進行老年代的垃圾收集,這樣停頓時間就很長了。因此說參數-XX:CMSInitiatingOccupancyFraction設置的太高將會很容易致使「Concurrent Mode Failure」失敗,性能反而下降。
3.最後一個缺點,CMS是基於「標記-清除」算法實現的收集器,使用「標記-清除」算法收集後,會產生大量碎片。空間碎片太多時,將會給對象分配帶來不少麻煩,好比說大對象,內存空間找不到連續的空間來分配不得不提早觸發一次Full GC。爲了解決這個問題,CMS收集器提供了一個-XX:UseCMSCompactAtFullCollection開關參數,用於在Full GC以後增長一個內存碎片的合併整理過程,可是內存整理過程是沒法併發的,所以解決了空間碎片問題,卻使停頓時間變長。還可經過-XX:CMSFullGCBeforeCompaction參數設置執行多少次不壓縮的Full GC以後,跟着來一次碎片整理過程(默認值是0,表示每次進入Full GC時都進行碎片整理)。
7.G1收集器
G1收集器具體介紹:https://www.cnblogs.com/jing99/p/6072059.html
五.GC日誌查看
2018-04-24T09:52:41.903+0800: 20.076: [GC (Metadata GC Threshold) [PSYoungGen: 1208285K->104653K(1273344K)] 1257730K->154106K(4135424K), 0.1108126 secs] [Times: user=0.20 sys=0.02, real=0.11 secs]
20.076::GC發生的時間,這個數字的含義是從Java虛擬機啓動以來通過的秒數;
[GC或[Full GC:此次垃圾收集的停頓類型,不是用來區分新生代GC和老年代GC的,若是有Full表明此次GC是發生了Stop-The-World,若是是[Full GC (System)則說明是調用System.gc()方法所觸發的收集;
[PSYoungGen:表示GC發生的區域,這裏顯示的區域名稱與使用的GC收集器是密切相關的。
區域 | GC收集器 |
[DefNew |
Serial收集器中的新生代名爲「Default New Generation」 |
[Tenured |
|
[Perm |
|
[ParNew |
Parallel New Generation |
[PSYoungGen |
Parallel Scavenge收集器 |
方括號內的1208285K->104653K(1273344K):GC前該內存區域已使用容量 -> GC後該內存區域已使用容量(該內存區域總容量)
方括號外的1257730K->154106K(4135424K):GC前Java堆已使用容量 -> GC後Java堆已使用容量(Java堆總容量)
0.1108126 secs:表示該內存區域GC所佔用的時間,單位是秒。
[Times: user=0.20 sys=0.02, real=0.11 secs]:更具體的時間。用戶態消耗的CPU時間、內核態消耗的CPU時間、操做從開始到結束所通過的牆鍾時間。
牆鍾時間包括各類非運算的等待耗時,但當系統有多CPU或者多核的話,多線程操做會疊加這些CPU時間,因此讀者看到user或sys時間超富哦real時間是徹底正常的。
2018-04-24T09:52:42.014+0800: 20.187: [Full GC (Metadata GC Threshold) [PSYoungGen: 104653K->0K(1273344K)] [ParOldGen: 49452K->129000K(2862080K)] 154106K->129000K(4135424K), [Metaspace: 58095K->58034K(1101824K)], 0.2967997 secs] [Times: user=0.88 sys=0.05, real=0.30 secs]
2018-04-24T09:52:45.314+0800: 23.487: [GC (Allocation Failure) [PSYoungGen: 1121792K->68747K(1282560K)] 1250792K->197755K(4144640K), 0.0823790 secs] [Times: user=0.14 sys=0.00, real=0.08 secs]