原文連接:http://www.dubby.cn/detail.html?id=9062html
自動垃圾收集是查看堆內存的過程,能夠識別哪些對象正在使用,哪些不是,以及刪除未使用的對象。一個正在使用的對象或一個被引用的對象,意味着你的程序的某個部分仍然保持着一個指向這個對象的指針。未使用的對象或未引用的對象再也不被程序的任何部分引用。因此未被引用的對象所使用的內存能夠被回收。java
在像C這樣的編程語言中,分配和釋放內存是一個手動過程。在Java中,釋放內存的過程由垃圾收集器自動處理。基本過程能夠描述以下。算法
這個過程的第一步就是標記。這是垃圾收集器標記內存中哪些對象正在被使用,哪些對象已經沒有被使用。數據庫
有用的對象顯示爲藍色,沒有用的對象顯示爲黃色。在標記階段掃描全部對象,而後作出這個決定。若是必須掃描系統中的全部對象,這多是很是耗時的過程。編程
內存維護着一個空閒內存列表,每次分配空間時,會來這個列表上找到合適的空間分配。正常刪除時,會把沒有用到的對象的內存空間還給空閒列表。bash
爲了進一步提升性能,除了刪除未引用的對象以外,還能夠壓縮剩餘的引用對象。 經過移動被引用的對象,這使得新的內存分配變得更容易和更快。服務器
如前所述,標記和壓縮JVM中的全部對象效率不高。 隨着愈來愈多的對象被分配,對象列表的增加和增加致使更長和更長的垃圾收集時間。 然而,應用程序的實證分析代表,大多數對象是短暫的。網絡
這裏給個數據的例子。多線程
正如你所看到的,隨着時間的推移對象保持存活的愈來愈少。 實際上,大多數對象的壽命都很短,如圖左側較高的值所示。併發
根據上面的對象的行爲特性,咱們能夠總結出一個更好的方式來提升JVM垃圾回收的效率。因此,就把堆內存分紅幾種代,新生代,老年代,永久代(Java8以後就沒有永久代了,取而代之的是元數據Metaspace)。
一個新的對象會被分配在新生代上,而且新的對象會在新生代裏慢慢變老。當新生代的空間被佔滿後,就會觸發一次minor gc。假設新生代裏的對象死亡率很高的話,那麼新生代的垃圾回收就是很優的。一個充滿死亡對象的新生代收集起來其實很快。倖存下來的對象會慢慢變老,直到能夠移入老年代。
Stop the World Event——全部的新生代手機都是中止世界的事件。Stop the World Event的意思是,全部的應用程序的線程都會被暫停,直到垃圾回收完成。新生代GC老是Stop the World。
老年代是存放那些經歷了屢次minor gc,年紀達到一個閾值以後的存活的對象。通常來講,會給對象設置一個年齡閾值,達到閾值以後,就會移入老年代。最後,老年代須要進行垃圾回收,就會觸發一次major gc。
Major gc也是致使Stop the World。在大部分狀況下,major gc是會比minor gc慢不少。因此,對於一個關注響應時間的應用來講,應該儘量的下降major gc的次數。這裏也要注意到,major gc的停頓時間(Stop the World的時間)是和你選取的垃圾收集器有關的。
永久代包含了JVM所須要的class和method的定義等元數據。永久代會隨着JVM運行時加載的class而填充新的元數據。除此以外,Java SE的類庫也會被存儲在這裏。
若是JVM檢測到這部分class不會被使用了,並且須要更多的內存空間來加載其餘的class,那麼class也會被回收(unloaded/卸載)。這個收集包含在一次full gc中。(即使是在Java8以後,沒有了所謂了永久代,取而代之的是元數據,可是,也會存在類型卸載的回收)
如今你已經明白了爲何須要把堆細分紅不一樣的幾個代,如今是時候仔細的看看這種空間是如何工做的了。下面的圖演示了在JVM中,對象的分配和變老的過程。
1.首先,任何對象都會被分配在eden區。兩個suvivor區一開始都是空的。
這裏是爲了給讀者介紹垃圾回收器的設計過程,和一步步的思考過程,在以後仍是會有不少優化,可能會和一開始的設計意圖相違背,請見諒。好比,有的對象甚至不分配到堆裏(逃逸分析),有的大對象甚至會直接分配到old區(大對象分配),有的對象甚至會分配到堆外內存(nio等),等等各類特殊狀況。
2.當eden滿了以後,就會觸發一次minor gc。
3.活着的對象會被移到第一個suvovor區(第一個第二個都是相對的)。沒有被是用的對象就直接被清除了。
4.下一次minor gc發生時,一樣的操做。沒有被使用的對象被清除,活着的對象和被移到另外一個suvivor區。並且,這些對象年齡會+1,而後被移入第二個suvivor。全部的活着的對象都被移入這個新的suvivor1,那麼eden和suvivor0又都空了。可是,如今在suvivor1中,對象的年齡是不同的。
5.下一次minor gc,又會重複上面的步驟。不過對象是從eden和suvicor1移入到suvivor0中了。
6.終於,隨便不斷的minor gc,對象的年齡愈來愈大,達到了閾值(這裏是8)時,他們會晉升帶老年代。
7.隨着更多的minor gc,也有更多的對象晉升到老年代。
8.上面已經涵蓋了新生代的整個過程。最後,老年代須要進行一次major gc來清除,壓縮老年代的空間。
上面說了那麼多,相信機智的讀者已經大體瞭解垃圾回收的過程了。如今讓咱們親眼看一看這個執行過程。這一部分,咱們會運行一個Java應用程序,而後使用Visual VM分析回收的過程。Visual VM是JDK提供給咱們的一個工具,開發者可使用這個工具對JVM進行各個方面的監視。
確保你的電腦已經安裝了JDK,並下載了上一步說的demo。而後解壓到本地一個目錄下。個人目錄是/Users/teeyoung/Desktop/code4me/javademos8
。
而後執行Java2demo.jar,java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2Demo.jar
注意:1.這些命令稍後會解釋;2.-XX:PermSize=20m -XX:MaxPermSize=20m若是是在Java 8以後是提示無效,覺得已經被移除了。
程序運行起來是這個樣子:
你能夠看到不少tab,那些演示了Java的繪圖功能(看到這個程序,讓我想到了買家秀和賣家秀,別人寫的代碼和我寫的代碼)。
隨意點擊各個tab,大體是這樣的:
這個界面能夠看到垃圾回收行爲的結果,看右下角的內存監視。咱們先讓他運行着,咱們稍後會用到它的。
若是你的jdk/bin已經在你的path下了,那麼直接執行jvisualvm
,不然須要輸入完整的路徑,如:/usr/bin/jvisualvm
。
咱們須要安裝Visual GC這個插件,可是java.net這個站點都關了,沒法聯網安裝,因此我寫了另外一篇文章演示如何安裝插件。請移步jvisualvm插件安裝的正確姿式(解決網絡問題):http://www.dubby.cn/detail.html?id=9061。
安裝以後就是這個樣子:
首先雙擊Java2Demo這個本地進程,或者右擊->Open:
而後點擊Visual GC這個tab:
而後就自由嘗試各個tab頁,看看每一個信息表明JVM的什麼指標。還有,你能夠嘗試着改變Java2Demo上的string和image的數量,看看對垃圾回收有什麼影響。
如今你知道了垃圾回收的基本概念,還有如何去監視JVM的垃圾回收。如今咱們來了解Java給咱們提供的不一樣的垃圾回收器,還有咱們須要掌握如何使用這些垃圾回收器的命令行。
這裏給出一些通用的命令行,無論你是什麼收集器,都會用到的。
選項 | 描述 |
---|---|
-Xms | 設置JVM啓動時,堆的初始大小 |
-Xmx | 設置堆的最大的容量 |
-Xmn | 設置新生代的容量 |
-XX:PermSize | 設置永久代的初始大小(Java 8以廢棄) |
-XX:MaxPermSize | 設置永久代的最大容量(Java 8以廢棄) |
-XX:MinHeapFreeRatio | 設置堆最小空閒容量,低於這個閾值就擴容,可是堆總量仍是要在Xmx和Xms之間 |
-XX:MaxHeapFreeRatio | 設置堆最大空閒容量,高於這個閾值就收縮,可是堆總量仍是要在Xmx和Xms之間 |
Serial收集器是客戶端默認的收集器。使用Serial收集器,minor gc和major gc都是單線程處理。並且,老年代使用併發-壓縮算法。把老年代的活着的對象移到老年代的前面,後面空出空閒區域,以供後續分配,能夠避免空間碎片。
Serial收集器是一些客戶端(PC,不是服務器)應用使用,並且對於低延時要求不高的。他的優點是單線程處理。直到今天,對於一些不是很重要,堆內存只有幾百MB的應用來講,Serial GC依然是個頗有效的垃圾收集器。
還有一個普遍使用Serial收集器的場景是,一個機器上運行着不少JVM(在某些場景下,JVM的數量比處理器的核數還要多)。在這種狀況,使用Serial收集器能夠減小JVM之間衝突,即使GC的時間變長了。
最後,隨着嵌入式設備的普及,內存少,核數少,Serial收集器可能會從新綻開光彩。
開始Serial收集器:
-XX:+UseSerialGC
複製代碼
給個完整的例子:
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2demo.jar
複製代碼
Parallel收集器在回收新生代時,使用多線程進行收集。默認的回收線程數等於機器的核數。可使用-XX:ParallelGCThreads=<desired number>
來設置但願的線程數。
在只有一個CPU的機器上,即使你已經開啓了Parallel收集器,JVM仍是會使用默認的收集器來工做。
Parallel收集器也被稱爲吞吐收集器。由於他能夠利用多線程來加快應用的吞吐。這個收集器通常被用做有不少工做須要作,並且對低延要求時不那麼高的應用。例如,批處理(報表,帳單,或者是很大的數據庫查詢等)。
這個命令行選項是開啓新生代的多線程收集,老年代的多線程收集。老年代也是整理方式。
給個完整的例子:
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelGC -jar Java2demo.jar
複製代碼
這個選項是開啓新生代的多線程收集,老年代的多線程收集。老年代也是整理方式。
整理:就是會把活着的對象移到內存的前面,這樣就對象和對象之間的小的空閒的空間(內存碎片)。內存碎片可能致使,空閒空間足夠,可是大對象沒法分配的狀況。
完整的例子:
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelOldGC -jar Java2demo.jar
複製代碼
併發(Concurrent)標記(Mark)清除(Sweep)收集器(CMS)(也被叫作:併發低延時收集器)是一個手機老年代的垃圾收集器。他試圖把大部分垃圾收集工做和應用程序的線程併發執行,以下降所形成的停頓(Stop the World)時間。一般狀況下,CMS不會壓縮整理活着對象。因此,會存在內存碎片的問題。若是內存碎片成爲你的問題,那麼能夠考慮換用更大的堆(哈哈,也能夠考慮換收集器,可是,換更大的堆是直接而且簡單的方法)。
注意:CMS收集器在新生代的收集方式和Parallel在新生代的收集方式同樣(單線程,複製)。
CMS適用對低延時有高要求的應用。好比,響應事件的桌面應用,響應請求的Web服務器,或者響應查詢的數據庫。
開啓命令:
-XX:+UseConcMarkSweepGC
複製代碼
設置線程數:
-XX:ParallelCMSThreads=<n>
複製代碼
這裏給個完整的例子:
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseConcMarkSweepGC -XX:ParallelCMSThreads=2 -jar Java2demo.jar
複製代碼
具體的能夠查看【淺度渣文】JVM——G1收集器:http://www.dubby.cn/detail.html?id=9059
這裏簡單描述一下吧,G1在Java 7纔出現的,是一個併發的,低延時的,整理收集器。對堆內存管理和以前的收集器都不同。
開啓命令:
-XX:+UseG1GC
複製代碼
完整的例子:
java -Xmx12m -Xms3m -XX:+UseG1GC -jar Java2demo.jar
複製代碼