垃圾收集器關注的是Java堆和方法區這部份內存。java
GC大概須要關注的事情有:算法
哪些內存須要回收
何時回收
怎樣回收
複製代碼
給一個對象添加一個引用計數器,每當有個地方引用時,計數器加1;引用失效時,計數器減1;引用計數器爲0的對象不可能再被使用。緩存
實現簡單,判斷效率高。
複製代碼
難解決對象之間存在循環引用的場景。
複製代碼
主流的Java虛擬機沒有用引用計數器服務器
以GC Roots對象做爲起始點,從這些節點開始向下搜索,搜索走過的路徑爲引用鏈。從GC Roots到這個對象不可達時,說明此對象不可用,會被判斷爲可回收的對象。多線程
虛擬機棧中引用的對象【棧幀中的本地變量表】
方法區中類靜態屬性引用的對象
方法區中常量引用的對象
本地方法棧中引用的對象
複製代碼
JDK1.2以後Java對引用作了進一步的細分。由強到弱依次分爲:強引用、軟引用、弱引用、虛引用。併發
廣泛存在的,相似於ide
Object obj = new Object();
複製代碼
只要強引用還存在,垃圾收集器就不會回收被引用的對象性能
咱們能夠將對象的引用顯示地置爲null:o=null; 【能夠幫助垃圾收集器回收此對象】this
描述一些可能用到的對象可是非必需的。對於這種引用,在內存充足的時候垃圾回收器不會回收他,在內存不足的時候會回收。spa
軟引用很是適合於建立緩存。當系統內存不足的時候,緩存中的內容是能夠被釋放的。
在Java中用java.lang.ref.SoftReference類來表示。
// 獲取對象並緩存
Object object = new Object();
SoftReference softRef = new SoftReference(object);
// 從軟引用中獲取對象
Object object = (Object) softRef.get();
if (object == null){
// 當軟引用被回收後從新獲取對象
object = new Object();
}
複製代碼
弱引用用來描述非必需對象,其強度比軟引用更弱。被弱引用關聯的對象只能活到下次垃圾收集器回收以前,無論內存是否充足。
若是這個對象是偶爾的使用,而且但願在使用時隨時就能獲取到,但又不想影響此對象的垃圾收集,那麼你應該用 Weak Reference 來記住此對象。 能夠解決內存泄露問題。
例如對象池、緩存中的過時對象都有可能引起內存泄露的問題。用 WeakHashMap 來做爲緩存的容器能夠有效解決這一問題。WeakHashMap 和 HashMap 幾乎同樣,惟一的區別就是它的鍵使用弱引用。當 WeakHashMap 的鍵標記爲過時時,這個鍵對應的條目就會自動被移除。這就避免了內存泄漏問題。
虛引用也稱爲幻影引用,是最弱的一種引用方式。
一個對象是都有虛引用的存在都不會對生存時間都構成影響,也沒法經過虛引用來獲取對一個對象的真實引用。
惟一的用處:能在對象被GC時收到系統通知,JAVA中用PhantomReference來實現虛引用。
String temp = "hello world";
ReferenceQueue<String> queue = new ReferenceQueue<String>();
PhantomReference<String> phReference = new PhantomReference<String>(temp, queue);
System.out.println(phReference.get());//get返回null
複製代碼
不一樣分類的引用,讓內存管理更容易,不一樣的對象實例有不一樣的回收管理方式
對對象進行可達性分析發現他到GC ROOTS沒有可達的引用鏈,就會做爲GC回收的對象,若是到GC Roots可達,那麼就還沒死,不會回收。
可是即便到GC Roots對象不可達,對象也還有自我救贖的機會,也並不是死亡。
若是重寫了finalize方法,而且從新指向該對象,該對象仍是存活,不會死亡。若是這個自我救贖的機會也錯失,那麼通常都會被回收掉。
public class FinalizeTest {
public static FinalizeTest testFinalize = null;
@Override
public void finalize() throws Throwable {
super.finalize();
System.out.println("正在執行finalize方法~!");
//自救
testFinalize = this;
}
public static void main(String[] args) throws InterruptedException {
testFinalize = new FinalizeTest();
//對象第一次成功拯救本身
testFinalize = null;
System.gc();
//由於finalize()方法優先級很低,因此暫停1S等待它
Thread.sleep(1000);
//finalize()方法確實被GC觸發了,可是收集前成功逃脫了。
if (testFinalize != null) {
System.out.println("alive~!");
} else {
System.out.println("dead~!");
}
//對象第二次成功拯救本身未遂,由於任何一個對象的finalize()只會被系統調用一次。
testFinalize = null;
System.gc();
//由於finalize()方法優先級很低,因此暫停1S等待它
Thread.sleep(1000);
if (testFinalize != null) {
System.out.println("alive~!");
} else {
System.out.println("dead~!");
}
}
}
複製代碼
finalize方法實際中通常不會使用,運行代價大,不肯定性大,能夠用try-finally更好的關閉外部資源。
永久代主要可回收的兩部分分別是:
廢棄的常量
無用的類
複製代碼
無用類要同時知足3個條件:
該類的全部實例都被回收掉
加載該類的ClassLoader被回收
對應的Class對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該類方法
複製代碼
HotSpot虛擬機提供了-Xnoclassgc參數進行控制是否回收無用。
還可使用-verbose:class及-XX:+TraceClassLoading、 -XX:+TraceClassUnLoading查看類的加載和卸載信息。
一、標記階段。從根集合開始掃描,標記存活的對象
二、清除階段。掃描整個內存空間,回收未被標記的對象,使用free-list記錄被釋放的區域
實現簡單
不須要額外的空間
複製代碼
效率問題,標記、清除兩個階段掃描性能不高
會產生內存碎片。分配大對象時,沒法找到匹配的內存,會致使另外一次垃圾收集的觸發
複製代碼
針對老年代的CMS收集器;
複製代碼
從根集合開始掃描,從一塊內存中找到存活的對象,複製到另外一塊空閒的內存中,而後回收第一塊內存中的對象。下次這兩塊內存交換身份。
實現簡單,運行高效,沒有標記
沒有內存碎片
複製代碼
內存使用縮小爲原來的一半
複製代碼
如今商業JVM都採用這種算法=來回收新生代;
Serial收集器、ParNew收集器、Parallel Scavenge收集器、G1;
複製代碼
一、標記階段。和標記清除算法的同樣
二、整理階段。讓全部存活的對象向一端移動,而後直接清理掉端外界邊的內存
沒有內存碎片
複製代碼
須要移動對象,增長成本
複製代碼
不少垃圾收集器採用這種算法來回收老年代;
如Serial Old收集器、G1;
複製代碼
當前商業虛擬機基本上都是採用分代垃圾回收算法來回收垃圾,思想也很簡單,就是根據對象的生命週期將內存劃分,而後進行分區管理,根據各個年代的特色採用最合適的收集算法。
通常把Java堆分爲新生代和老年代;
每次垃圾收集都有大批對象死去,只有少許存活;
因此可採用複製算法;
複製代碼
對象存活率高,沒有額外的空間能夠分配擔保;
使用"標記-清理"或"標記-整理"算法;
複製代碼
根據各個年代的特色採用最適當的收集算法
複製代碼
仍然不能控制每次垃圾收集的時間;
複製代碼
目前幾乎全部商業虛擬機的垃圾收集器都採用分代收集算法;
如HotSpot虛擬機中所有垃圾收集器:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1;
複製代碼
JVM在進行GC時,並不是每次都對新生代、舊生代、永久代一塊兒回收的,大部分時候回收的都是指新生代。所以GC按照回收的區域又分了兩種類型,一種是普通GC(minor GC),一種是全局GC(major GC or Full GC),它們所針對的區域以下。
普通GC(minor GC):只針對新生代區域的GC。
全局GC(major GC or Full GC):針對年老代的GC,偶爾伴隨對新生代的GC以及對永久代的GC。
複製代碼
因爲年老代與永久代相對來講GC效果很差,並且兩者的內存使用增加速度也慢,所以通常狀況下,須要通過好幾回普通GC,纔會觸發一次全局GC。
單線程的收集器,進行垃圾收集時,必須暫停其餘的工做線程,也就是「Stop The World」。
使用串行回收;複製算法
單CPU、新生代小、對暫停時間要求不高的應用
Client模式下的默認新生代收集器
複製代碼
-XX:+UseSerialGC 串行收集器
複製代碼
ParNew收集器就是Serial收集器的多線程版本。經過多線程掃描並壓縮堆。
新生代並行,老年代串行;新生代複製算法、老年代標記-整理
Server模式下虛擬機中首選的新生代收集器
複製代碼
-XX:+UseParNewGC ParNew收集器
-XX:ParallelGCThreads 限制線程數量
複製代碼
新生代收集器,使用複製算法,多個線程來經過掃描並壓縮堆。
Parallel Scavenge收集器的目標是達到一個可控制的吞吐量,也稱吞吐量優先的收集器。
吞吐量 = 運行用戶代碼時間/(運行用戶代碼時間+垃圾收集器時間)
複製代碼
高吞吐量能夠高效的利用CPU,儘快完成程序任務,適合在後臺運算而不須要太多交互任務。
多CPU、對暫停時間要求比較短的應用
Server模式上的默認選擇
複製代碼
-XX:MaxGCPauseMillis 最大垃圾收集停頓時間
-XX:GCTimeRatio 設置吞吐量大小
-XX:+UseAdaptiveSizePolicy GC自適應的調節策略,把內存管理的調優任務交給虛擬機去完成
複製代碼
Serial Old收集器是Serial收集器的老年代版本,使用標記-整理算法
這個收集器主要在於給Client模式下的虛擬機使用。
若是在Server中,主要用途是:1,在JDK1.5前和Parallel Scavenge搭配使用。2,做爲Concurrent Mode Failure時候使用。
複製代碼
Parallel Scanvenge收集器的老年隊收集器,使用標記-整理方式。
在這個方式沒有產生以前,Parallel Scavenge只能選擇Serial Old。
因爲被拖了後腿,那麼Parallel Scavenge並不能在總體上獲取吞吐量最大化的效果。甚至比不上CMS+ParNew的吞吐量。
併發標記-清除算法。 以獲取最短回收停頓時間爲目標的收集器
初始化標記-stop the world【簡單標記下GC Roots能直接關聯到的對象】
併發標記-耗時【進行GC Roots Tracing 】
從新標記-stop the world【修正併發標記期間用戶程序繼續運行而致使標記發生變更那一部分對 象標記記錄】
併發清除-耗時
複製代碼
沒法處理浮動垃圾
對CPU資源敏感
會產生大量的空間碎片
複製代碼
重視服務器響應速度的應用
複製代碼
-XX:+UseConcMarkSweepGC 使用CMS收集器
-XX:+ UseCMSCompactAtFullCollection Full GC後,進行一次碎片整理;整理過程是獨佔的,會引發停頓時間變長
-XX:+CMSFullGCsBeforeCompaction 設置進行幾回Full GC後,進行一次碎片整理
-XX:ParallelCMSThreads 設定CMS的線程數量(通常狀況約等於可用CPU數量)
複製代碼
G1收集器時面向服務端應用的垃圾收集器。
在G1中,堆被劃分紅 許多個連續的區域(region)。採用G1算法進行回收,吸取了CMS收集器特色。
支持很大的堆,高吞吐量
可配置在N毫秒內最多隻佔用M毫秒的時間進行垃圾回收
並行和併發
分代收集
空間整合【總體上:標記-整理算法,局部上:複製算法】
可預測停頓【可配置在N毫秒內最多隻佔用M毫秒的時間進行垃圾回收】
複製代碼
初始標記;【標記GC Roots直接關聯的對象,stw】
併發標記;【進行GC Roots Tracing 】
最終標記;【再標記,會有短暫停頓(STW)。再標記階段是用來收集 併發標記階段 產生新的垃圾】
篩選回收【stw】
複製代碼
須要大堆空間、限制的垃圾回收延遲的應用
複製代碼
–XX:+UseG1GC 使用G1垃圾回收器
複製代碼