Java的一大特色就是虛擬機自己擁有垃圾回收機制,用戶在編程過程當中再也不須要考慮垃圾回收的事情。全部的對象的建立都是在堆空間中,垃圾回收主要針對堆空間。html
Java是面向對象的,程序運行中不斷有對象被建立和使用。絕大部分對象在使用後,就完成了歷史使命,等待GC回收。java
虛擬機進行垃圾回收的第一步是須要肯定堆空間中哪些對象能夠被回收,第二步是對對象所在的空間進行清理。根據不一樣的算法會考慮在清理完成後進行(壓縮)整理,防止內存碎片。算法
判斷對象是否能夠被回收的依據:編程
Java虛擬機中,GC Roots中的對象包括下面幾種:安全
這裏提到了引用的概念。java 1.2以後增豐富了引用的類別:強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)。數據結構
GC回收對象時,若是對象覆蓋了finalize()方法而且還沒執行,則將該對象進行一次標記,放到一個叫F-Queue中。沒有覆蓋finalize()或者已經執行過了finalize(),則該對象直接回收。虛擬機在售後會啓動單獨的線程在執行F-Queue中的對象的finalize(),目的是防止finalize()過於耗時影響GC時間。若是在finalize()期間該對象從新有了引用,則該對象逃脫GC,不然進行二次標記等待回收。多線程
採用可達性分析肯定哪些對象能夠被回收的過程,HosPot虛擬機採用三色標記算法,被標記的對象組成的路徑成爲引用鏈。標記以後就是對對象所處空間進行回收。併發
HosPot虛擬機中的垃圾收集的算法:性能
HosPot將堆空間分爲年輕代(YongGeneration)和年老代(Old/TenuredGeneration)。年輕代又分爲1個Eden區和2個Survivor區,默認是8:1:1。對象在堆空間內的流轉過程:.net
因爲處理器有多核多線程和單核單線程之分,用來執行垃圾清除的垃圾收集器也有多種:
幾個概念:
因爲年輕代和年老代的特色,除G1外,年輕代收集器均採用複製算法,年老代收集器均採用標記整理算法。(注:CMS單次是標記清除,必定次數後或者空間不足時觸發(壓縮)整理)
Serial收集器:
單線程執行,STW的時間比較長,通常應用與Client模式
設置參數: "-XX:+UseSerialGC":添加該參數來顯式的使用串行垃圾收集器;
ParNew收集器:
多線程執行,行爲、特色和Serial收集器同樣。由於除Serial外,目前只有它能與CMS收集器配合工做。
設置參數:
"-XX:+UseConcMarkSweepGC":指定使用CMS後,會默認使用ParNew做爲新生代收集器;
"-XX:+UseParNewGC":強制指定使用ParNew;
"-XX:ParallelGCThreads":指定垃圾收集的線程數量,ParNew默認開啓的收集線程與CPU的數量相同;
Parallel Scavenge收集器:
Parallel Scavenge垃圾收集器由於與吞吐量關係密切,也稱爲吞吐量收集器(Throughput Collector)。CMS、Parallel等收集器的目的是儘量的減小用戶線程等待時間,即便一段時間內發生屢次GC。而Parallel Scavenge收集器的目的是儘量提升虛擬機的吞吐量,儘量少發生GC,哪怕一個GC的時間比較長。通常應用於須要後臺計算的場景,好比批量處理,科學計算等。
設置參數:
"-XX:MaxGCPauseMillis"控制最大垃圾收集停頓時間,大於0的毫秒數;
"-XX:GCTimeRatio"設置垃圾收集時間佔總時間的比率,0<n<100的整數;至關於設置吞吐量大小;
"-XX:+UseAdptiveSizePolicy"開啓這個參數後,JVM會根據當前系統運行狀況收集性能監控信息,動態調整這些參數,以提供最合適的停頓時間或最大的吞吐量,這種調節方式稱爲GC自適應的調節策略(GC Ergonomiscs);
Serial Old收集器:
Serial收集器的年老代版,主要應用與Client模式。
Serial Old收集器在Server模式能夠做爲CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用
Parallel Old收集器:
ParNew收集器的年老代版。兩者均爲多線程並行。
設置參數: "-XX:+UseParallelOldGC":指定使用Parallel Old收集器;
CMS收集器(Concurrent Mark Sweep)
併發標記清理(Concurrent Mark Sweep,CMS)收集器也稱爲併發低停頓收集器(Concurrent Low Pause Collector)或低延遲(low-latency)垃圾收集器;併發收集器,用戶等待時間短,應用於WEB等Server端。標記整理會產生內存碎片。
設置參數:
"-XX:+UseConcMarkSweepGC":指定使用CMS收集器
"-XX:CMSInitiatingOccupancyFraction":設置CMS預留內存空間;JDK1.5默認值爲68%;JDK1.6默認值爲92%;
運做過程:
CMS的缺點:
G1收集器
特色:
設置參數
"-XX:+UseG1GC":指定使用G1收集器;
"-XX:InitiatingHeapOccupancyPercent":當整個Java堆的佔用率達到參數值時,開始併發標記階段;默認爲45;
"-XX:MaxGCPauseMillis":爲G1設置暫停時間目標,默認值爲200毫秒;
"-XX:G1HeapRegionSize":設置每一個Region大小,範圍1MB到32MB;目標是在最小Java堆時能夠擁有約2048個
可預測停頓的原理:
G1收集器之因此能創建可預測的停頓模型,是由於它能夠有計劃的避免在整個Java堆中進行全區域的垃圾收集。G1跟蹤各個Region裏面的垃圾堆積的價值大小(每次回收所得到的空間大小以及回收所須要的時間),在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的Region(這也就是Garbage-First名稱的由來)之中使用Region劃份內存空間以及有優先級的區域回收方式,保證了G1收集器在遊俠時間誒能夠獲取儘量高的手機效率。
一個對象被不一樣區域引用的問題:
因爲有衆多的region區域,當一個對象被其餘區域飲用時,在作可達性判斷肯定對象是否存活的時候,豈不是要掃描整個Java堆?這個問題並不是只有G1有,只是G1有衆多的區域顯得問題比較突出。 解決辦法:
不管是G1仍是其餘分代收集器,JVM都採用RememberedSet來避免全堆掃描。G1中每一個Region都有一個與之對應的RememberedSet,當虛擬機發現程序在對Reference類型的數據進行寫操做時,會產生一個WriteBarrier暫時中斷寫操做,檢查Reference引用的對象是否處於不一樣的Region之中(在分代的例子中就是檢查是否老年代中的對象引用了新生代中的對象)。若是是,便經過CardTable把相關引用信息記錄到被引用對象所屬的Region的RememberedSet中。當進行內存回首時,在GC根節點的枚舉範圍中加入RememberedSet便可保證不對全堆掃描也不會有遺漏。
若是不計算維護RememberedSet的操做,G1收集器的運做過程:
G1收集器要求堆空間在6G以上才使用。
方法區的回收針對類型的卸載。類型的卸載須要知足3個條件:
動態代理,動態生成JSP等頻繁自定義ClassLoader生成class字節碼的場景使得虛擬機必須具有類卸載的功能。什麼時候對該空間進行回收由GC判斷。
Jdk1.8以後字符串常量池位於堆空間單獨一塊,字符串常量池空間也是能夠被回收的,只須要判斷該字符串是否仍有引用就能夠肯定是否能夠回收。什麼時候對該空間進行回收由GC判斷。
用戶線程狀態
只要發生GC,就會致使Stop The World。當發生Stop The World的時候,虛擬機中的用戶線程是什麼狀態呢?兩種:
1. 安全點(針對處於running狀態的線程)
由於程序在運行時不是全部的時候停下來都是安全的(好比運算進行到一半,數據值是一個髒數據),安全點是全部線程在」Stop the world」時到達的一個安全的點。因爲堆中的對象龐大,若爲每一個對象都生成OopMap數據結構將佔用大量空間,因此HotSpot只在」安全點「上生成這些數據結構。同時程序並不是在全部位置均可以中止,而是隻能在安全點纔會中止。因此」安全點「還會影響到GC的及時性。」安全點「選取時不能太多,以形成空間上的支出,也不能太少,以讓GC等待較長時間。 對於」安全點「還有另一個須要考慮的問題是,當GC發生時如何讓全部線程都跑到最近的」安全點「,通常都兩種方案。搶先式中斷,搶先式中斷是虛擬機將全部線程停下來,而後一一檢查是否已達安全點,若沒有到達安全點則恢復線程讓其到達最近的」安全點」,此種方式幾乎沒有虛擬機使用。主動式中斷的思路是中斷髮生時,虛擬機在全部的線程上設置一個標誌,線程自行檢查該標誌,而後進入「安全點」。檢查標誌的地方和安全點是重合的。
2. 安全區域(針對處於非running狀態的線程)
上面沒有解決的問題是當一個線程處於休眠,或未分配CPU時鐘,好比sleep或blocked狀態時,他就沒法走到安全點去掛起本身。對於這種狀況,就須要經過安全區域來解決,安全區域是一個程序不會更改本身引用的區間,在這個區域的任何地方開始GC都是安全的,能夠被認爲是擴展了的安全點。當線程執行到SafeRegion的代碼時,他就會標記本身已經進入Safe Region,此時發生GC,將不會管這些線程。快要離開安全區域的時候他就會去檢查當前的狀態,若是是在GC中,那邊他就會掛起等待能夠離開Safe Region的信號,不然他就能夠繼續運行。
參考資料: