雖然內存的分配和回收技術已至關成熟,但若是須要排查內存溢出、內存泄露問題,或者要求高併發、高性能時,就須要對垃圾的回收進行監控和調節,以更好優化系統提升性能。html
Java
內存結構中,程序計數器、虛擬機棧、本地方法棧等隨着線程而生,隨線程而滅,不須要考慮內存回收問題。而 Java
堆和方法區則不一樣,它們的內存分配是動態的,只有在運行期間才能知道會建立哪些對象,垃圾回收關注的就是這兩部分。java
垃圾回收首先須要判斷哪些對象還存活着,主要有引用計數和可達性分析兩種算法。算法
它的原理以下:給對象添加一個引用計數器,每當有一個地方引用它時,計時器值就加 1
;當引用失效時,計數器值就減 1
;若是計數器爲 0,對象就不可能再被使用。數組
引用計數算法雖然實現簡單、斷定效率較高。但它很難解決對象之間循環引用的問題。緩存
例如兩個對象相互引用,實際上兩個對象都不會再訪問,但由於相互引用着對方,致使它們的計數器值都不爲 0
,因而引用技術算法沒法經過 GC
收集器回收它們。併發
它的原理以下:經過一系列稱爲 GC Roots
的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到 GC Roots
沒有任何引用鏈相連時,則證實對象是不可用的。高併發
Java
中,可做爲 GC Roots
的對象包括以下幾種:性能
JNI
( Native
方法) 引用的對象。能夠看到,對象回收斷定算法判斷對象是否存活都與引用有關。從 JDK1.2
開始,引用分爲四種類型,用來實現不一樣的功能,它們的引用強度也依次遞減。優化
強引用(Strong Reference)線程
平時使用的引用就是強引用。只要強引用還存在,該對象永遠不會被回收。
能夠經過將對象設置爲 null
,使其被回收。
軟引用(Soft Reference)
用於描述一些還有用但並不是必需的對象。當系統內存空間不足時,會回收這些軟引用指向的對象。它經過 SoftReference
類來實現軟引用。
能夠用來實現高速緩存。
弱引用(Weak Reference)
用來描繪非必需對象。被弱引用指向的對象只能生存到下一次垃圾回收以前。只要垃圾收集器運行,弱引用指向的對象就會被回收。它經過 WeakReference
類來實現弱引用。
虛引用(Phantom Reference)
虛引用和沒有引用沒有任何區別。一個對象是否有虛引用,不會影響其生存時間,也沒法經過虛引用獲取對象實例。它經過 PhantomReference
來實現虛引用。必須和引用隊列 ReferenceQueue
聯合使用。
爲一個對象設置虛引用的惟一目的是該對象被垃圾收集器回收前會收到一條系統通知。
方法區,或者說 HotSpot
虛擬機中的永久代,進行垃圾回收的效率通常比較低。回收主要包括兩部份內容:廢棄常量和無用的類。
判斷一個常量是不是廢棄常量比較簡單,與回收 Java
堆中的對象相似。而斷定一個類是不是無用的類須要知足三個條件:
ClassLoader
已經被回收;java.lang.Class
對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該類的方法。標記-清除算法分爲兩個標記和清除階段:首先標記出全部須要回收的對象,在標記完成後統一回收全部被標記的對象。標記過程也就是對象存活斷定算法。
它是最基礎的收集算法,主要有兩個缺點:
複製算法將可用內存分爲大小相等的兩塊,每次只使用其中的一塊。在一塊內存用完後,將仍存活的對象賦值到另外一塊上面,再把已使用過的內存一次清理掉。
複製算法的優缺點以下:
標記-整理算法分爲標記和整理兩個階段,標記階段和「標記-清除算法」同樣,但在整理階段,不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。
標記-整理算法的優缺點以下:
分代算法根據對象存活週期將內存劃分爲幾塊。通常是將 Java
對分爲新生代和老年代,根據各個年代的特色採用適當的收集算法。
新生代中,每次垃圾收集時只有少許對象存活,選擇複製算法;老年代中,對象存活率較高、沒有額外空間進行分配,使用「標記-清理」或「標記-整理」算法。
爲了對不一樣生命週期的對象採用不一樣的回收算法,因此垃圾收集器都採用分代收集算法,將堆分爲新生代和老年代。
新生代主要用來存放新建立的對象,通常佔堆 1/3
的空間。因爲不少對象生命週期很短,每次 Minor GC
後只有少許對象存活,因此選用複製算法。
新生代又被分爲一塊較大的 Eden
區和兩塊較小的大小相等的 Survivor
區,使用 from
和 to
來分別指代兩個 Survivor
區。HotSpot
虛擬機默認 Eden
和兩塊 Survivor
的大小比例爲 8:1:1
。每次只會使用 Eden
和其中一塊 Survivor
區爲對象服務,因此老是有一塊 Survivor
區是空閒的,新生代實際可用的內存空間也就爲 90%
。
一般,對象會分配在 Eden
區中,當 Eden
區沒法在分配對象時,JVM
便會觸發一次 Minor GC
,將存活下來的對象複製到 from
指向的 Survivor
區中。
當 from
指向的 Survivor
區也沒法分配時,對 Eden
和 from
指向的 Survivor
區執行 Minor GC
,將存活下來的對象複製到 to
指向的 Survivor
區中,而後交換 from
和 to
指針,使 to
指向的 Survivor
區爲空,以保證下次 Minor GC
有複製的空閒空間。
老年代用於存放大對象,或年齡超過必定程度的對象。通常佔據堆 2/3
的空間。
若是對象須要大量連續的內存空間,例如很長的字符串及數組,這些對象會直接分配在老年代,以免在 Eden
區及兩個 Survivor
區之間發生大量的內存複製。
虛擬機爲每一個對象定義了一個對象年齡計數器,若是對象分配在 Eden
區,在通過一次 Minor GC
後仍然存活,以後移動到 Survivor
空間中,將其年齡設置爲 1
。對象在 Survivor
區中每通過一次 Minor GC
,年齡就增長一次,當它的年齡增長到必定程度(默認爲 15
)時,也會被晉升到老年代中。
若是在 Survivor
區中相同年齡全部對象大小的總和大於 Survivor
區的一半,年齡大於或等於該年齡的對象就能夠直接進入老年代。
老年代的對象通常都比較穩定,Major GC
不會頻繁執行。Major GC
採用標記—清除算法:首先掃描一次全部老年代,標記出存活的對象,而後回收沒有標記的對象。MajorGC
的耗時較長,並且會產生內存碎片。
Minor GC(Young GC)
指發生在新生代的垃圾收集動做。當 Eden
區沒有足夠的空間分配時,就會觸發一次 Minor GC
。因爲 Java
對象大多生命週期較短,因此 Minor GC
很是頻繁,通常回收速度也比較快。
Major GC
指發生在老年代的垃圾收集動做,在進行 Major GC
前,通常都會進行至少一次 Minor GC
。Major GC
的速度通常會比 Minor GC
慢 10
倍以上。
Full GC
指回收整個新生代和老年代的垃圾收集動做。成本較高,對系統性能產生影響。FULL GC
的時候會 STOP THE WORD
。
它的觸發條件主要有:
Minor GC
以前,若是老年代最大可用的連續空間小於歷次晉升到老生代對象的平均大小,則觸發一次 Full GC
。System.gc()
方法時。