Java與C++相比,具備動態分配內存和垃圾回收機制的技術優點,使得咱們不用把精力集中在內存的管理上,那咱們爲何還要去了解GC和內存分配呢?緣由很簡單:當須要排查各類內存溢出、內存泄漏問題時,當垃圾收集成爲系統達到更高併發量的瓶頸時,咱們就須要對這些「自動化」的技術實施必要的監控和調節。java
1.爲何進行垃圾回收算法
隨着程序的運行,系統內存中存在的對象實例、各類變量愈來愈多,若是不進行垃圾回收,會影響到程序的性能,當佔用內存過多時,還會產生OOM等系統異常。多線程
2.哪些內存須要回收併發
關於JVM內存結構,分爲:程序計數器、虛擬機棧、本地方法棧、堆、方法區。其中前三者隨線程而生,隨線程而滅,棧中的棧幀隨着方法的進入和退出而有條不紊的執行者出棧和入棧的操做,當方法調用結束或線程結束時,內存就天然跟着回收了,因此只有堆和方法區須要GC。那如何鑑定GC的對象呢?簡單講:若是某個對象已經不存在任何引用,那麼它能夠被回收。高併發
3.何時進行回收性能
引用計數算法來判斷對象是否存活:給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值加1;引用失效則減1;任什麼時候刻計數器爲0的對象就是不能被使用的。這種算法並無在主流的Java虛擬機裏使用,由於它有一個很大的缺點:很難解決對象之間互相循環引用的問題。spa
可達性分析算法判斷對象是否存活:基本思路就是經過一系列的稱爲「GC Roots」的對象爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連,則證實此對象是不可用的。如圖所示:雖然object五、六、7相關聯,但它對於GC Roots來講是不可達的,因此是能夠回收的對象。線程
注:在Java語言中,能夠做爲GC Roots對象的包括如下幾種:虛擬機棧(棧幀中的本地變量表)中引用的對象、方法區中類靜態屬性引用的對象、方法區常量引用的對象、本地方法棧中Native方法引用的對象。設計
拓展:JDK 1.2以後,Java將引用的概念進行了擴充,分爲:強、軟、弱、虛,強度逐級遞減。對象
強(Strong Refence):指在程序代碼中廣泛存在的,相似「Object o = new Object()」這類引用,只要強引用在,垃圾回收器就永遠不會回收調被引用的對象。
軟(Soft Refence):用來描述一些還有用但非必須的對象。對於軟引用關聯着的對象,在系統將要發生內存溢出異常以前,會把它列入可回收的範圍內,回收以後內存仍不夠,纔會拋出內存溢出異常。
弱(Weak Refence):一樣是用來描述一些有用但非必須的對象,但比軟引用還弱,被弱引用關聯的對象,只能生存到下一次垃圾手機發生以前。
虛(Phantom Refence):最弱的一種引用關係,它的存在,徹底不會對其關聯的對象的生存時間構成影響,也沒法經過虛引用來取得一個對象的實例,它的惟一做用就是能在這個關聯對象被回收時收到一個系統通知。
生存仍是死亡?
即便是可達性分析算法中不可達,也並不是確認「死亡」,要真正宣告一個對象「死亡」,至少要經歷兩次標記過程:當發現不可達後,會進行第一次標記並進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法,當對象沒有覆蓋finalize()方法或finalize()方法已經被虛擬機調用過,虛擬機將視這兩種狀況爲「沒有必要執行」;若是這個對象被斷定爲有必要執行finalize()方法,將會被放入F-Queue隊列中,並在稍後由一個虛擬機自動創建的、低優先級的Finalizer線程去執行它,此處「執行」指觸發這個方法。finalize()方法是擺脫「死亡」的最後一次機會,稍後GC將對F-Queue中的對象進行第二次標記。
總結:什麼樣的類須要回收呢?知足條件:1>.該類的全部實例對象都已經被回收 2>.加載該類的ClassLoader已經被回收 3>.該類對應的反射類java.lang.Class對象沒有被任何地方引用。
4.如何回收
標記清除算法:分爲「標記」和「清除」兩個階段,標記全部須要回收的對象,而後清楚。缺陷:標記和清楚兩個階段的效率不高;產生大量的空間碎片,可能致使分配大對象時空間不足而提早進行一次GC。
複製算法:將內存分爲大小相等的兩塊,只用一塊,當一塊空間滿時會將活着的對象複製到另外一塊中,而後對原先使用過的那塊內存空間進行一次清理。優勢:運行高效,不用擔憂空間碎片的產生。缺陷:將內存一分爲二,代價稍高。BUT,在虛擬機的實際設計中並非這麼分的,由於新生代中絕大多數都是「朝生夕死」,因此不須要1:1劃分,而是分爲Eden:From Survivor:To Survivor(8:1:1),當內存回收時,將Eden和From Survivor中的存活對象複製到另外一塊Survivor中。
標記整理算法:當對象成活率很高時,複製算法就不合適了,例如老年代。標記整理算法是把被標記的對象都移動到一段,直接清理端界之外的內存。
分代收集算法:當前商業虛擬機都採用的此算法,根據新生代和老年代各自的特徵選擇對應的回收算法。新生代-複製算法;老年代-「標記-清除」或「標記-整理」。
5.內存回收的具體實現----垃圾收集器
注:若是兩個收集器間有連線,則能夠搭配使用。
Serial收集器:一個單線程收集器,除了只會使用一個CPU或一條垃圾收集線程去回收垃圾,更重要的是它在垃圾收集時會中止進程,知道收集結束,即「Stop The World」。固然也有優勢:簡單而高效(與其餘收集器的單線程比)。
ParNew收集器:Serial收集器的多線程版本,是運行在Server模式下的虛擬機中首選的新生代收集器,其中一個很重要的緣由是能夠和CMS搭配使用。
Parallel Scavenge收集器:一個並行的、使用複製算法、做用於新生代的收集器,它與CMS等收集器的不一樣在於關注點不同。CMS關注於盡量的縮短垃圾收集時用戶線程的停頓時間;而Parallel Scavenge收集器則是達到一個可控制的吞吐量(運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)),主要適合在後臺運算而不須要太多交互的任務。
Serial Old收集器:使用「標記-整理」算法的Serial收集器的老年代版本,此收集器的意義在於給Client模式下的虛擬機使用。
Parallel Old收集器:Parallel Scavenge的老年代版本,使用多線程和「標記-整理」算法。
CMS收集器:是一種以獲取最短回收停頓時間爲目標的收集器,基於「標記-清楚」算法實現,運做過程分爲四個步驟:初始標記、併發標記、從新標記、併發清除。
■ 初始標記:標記GC Roots能直接關聯到的對象,速度很快。
■ 併發標記:進行GC Roots Tracing(判斷對象是否可回收)的過程。
■ 從新標記:修正併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分對象的標記記錄。
■ 併發清除:清除可回收的對象。和併發標記過程一塊兒,均可以與用戶線程一塊兒工做。
CMS優勢:併發收集、低停頓。缺點也很明顯,以下:
● CMS對CPU資源很是敏感。在併發階段,雖不會致使用戶線程停頓,但會佔用CPU資源,致使程序變慢,總吞吐量降低。
● CMS沒法處理浮動垃圾,可能出現「Concurrent Mode Failure」失敗致使另外一次Full GC。由於併發清理階段和用戶線程同步運行,會不斷的產生新的垃圾,在被標記後,沒法在當次垃圾收集時對其進行回收,只能等到下次,這些就成爲「浮動垃圾」。JDK 1.5默認老年代使用68%空間就會激活CMS,之因此這樣留出空間也是由於併發的緣由。
● CMS基於「標記-清除」,產生大量空間碎片。
注:併發(Concurrent):指用戶線程與垃圾收集線程同時執行(不必定是並行,可能交替執行),用戶程序在繼續運行,而垃圾收集程序運行在另外一個CPU上。
並行(Parallel):指多條垃圾收集線程並行工做,但此時用戶線程仍然處於等待狀態。
G1收集器:一款面向服務端的垃圾收集器,具備以下特色:
▲ 並行與併發:集兩種方式的優點於一體。
▲ 分代收集 :同其餘收集器同樣,雖然G1不須要其餘收集器配合就能夠管理整個GC堆,但能採用不一樣方式去管理新生代和老年代,且效果不錯。
▲ 空間整合:總體來看基於「標記-整理」,局部來看基於「複製」;不管怎麼看也不會產生空間碎片。