【淺度渣文】JVM——簡述垃圾回收

原文連接:http://www.dubby.cn/detail.html?id=9062html

垃圾回收的簡單描述

什麼是自動垃圾收集?

自動垃圾收集是查看堆內存的過程,能夠識別哪些對象正在使用,哪些不是,以及刪除未使用的對象。一個正在使用的對象或一個被引用的對象,意味着你的程序的某個部分仍然保持着一個指向這個對象的指針。未使用的對象或未引用的對象再也不被程序的任何部分引用。因此未被引用的對象所使用的內存能夠被回收。java

在像C這樣的編程語言中,分配和釋放內存是一個手動過程。在Java中,釋放內存的過程由垃圾收集器自動處理。基本過程能夠描述以下。算法

第1步:標記

這個過程的第一步就是標記。這是垃圾收集器標記內存中哪些對象正在被使用,哪些對象已經沒有被使用。數據庫

image

有用的對象顯示爲藍色,沒有用的對象顯示爲黃色。在標記階段掃描全部對象,而後作出這個決定。若是必須掃描系統中的全部對象,這多是很是耗時的過程。編程

第2步:普通刪除

內存維護着一個空閒內存列表,每次分配空間時,會來這個列表上找到合適的空間分配。正常刪除時,會把沒有用到的對象的內存空間還給空閒列表。bash

image

另外一種第2步:刪除並壓縮

爲了進一步提升性能,除了刪除未引用的對象以外,還能夠壓縮剩餘的引用對象。 經過移動被引用的對象,這使得新的內存分配變得更容易和更快。服務器

image

爲何使用分代垃圾收集?

如前所述,標記和壓縮JVM中的全部對象效率不高。 隨着愈來愈多的對象被分配,對象列表的增加和增加致使更長和更長的垃圾收集時間。 然而,應用程序的實證分析代表,大多數對象是短暫的。網絡

這裏給個數據的例子。多線程

image

正如你所看到的,隨着時間的推移對象保持存活的愈來愈少。 實際上,大多數對象的壽命都很短,如圖左側較高的值所示。併發

JVM 的分代

根據上面的對象的行爲特性,咱們能夠總結出一個更好的方式來提升JVM垃圾回收的效率。因此,就把堆內存分紅幾種代,新生代老年代永久代(Java8以後就沒有永久代了,取而代之的是元數據Metaspace)。

image

一個新的對象會被分配在新生代上,而且新的對象會在新生代裏慢慢變老。當新生代的空間被佔滿後,就會觸發一次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等),等等各類特殊狀況。

image

2.當eden滿了以後,就會觸發一次minor gc。

image

3.活着的對象會被移到第一個suvovor區(第一個第二個都是相對的)。沒有被是用的對象就直接被清除了。

image

4.下一次minor gc發生時,一樣的操做。沒有被使用的對象被清除,活着的對象和被移到另外一個suvivor區。並且,這些對象年齡會+1,而後被移入第二個suvivor。全部的活着的對象都被移入這個新的suvivor1,那麼eden和suvivor0又都空了。可是,如今在suvivor1中,對象的年齡是不同的。

image

5.下一次minor gc,又會重複上面的步驟。不過對象是從eden和suvicor1移入到suvivor0中了。

image

6.終於,隨便不斷的minor gc,對象的年齡愈來愈大,達到了閾值(這裏是8)時,他們會晉升帶老年代。

image

7.隨着更多的minor gc,也有更多的對象晉升到老年代。

image

8.上面已經涵蓋了新生代的整個過程。最後,老年代須要進行一次major gc來清除,壓縮老年代的空間。

image

執行並觀察

上面說了那麼多,相信機智的讀者已經大體瞭解垃圾回收的過程了。如今讓咱們親眼看一看這個執行過程。這一部分,咱們會運行一個Java應用程序,而後使用Visual VM分析回收的過程。Visual VM是JDK提供給咱們的一個工具,開發者可使用這個工具對JVM進行各個方面的監視。

1. 你須要先去Oracle官網下載JavaDemo

2.啓動示例代碼

確保你的電腦已經安裝了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以後是提示無效,覺得已經被移除了。

程序運行起來是這個樣子:

image

你能夠看到不少tab,那些演示了Java的繪圖功能(看到這個程序,讓我想到了買家秀和賣家秀,別人寫的代碼和我寫的代碼)。

隨意點擊各個tab,大體是這樣的:

image

這個界面能夠看到垃圾回收行爲的結果,看右下角的內存監視。咱們先讓他運行着,咱們稍後會用到它的。

3.啓動VisualVM

若是你的jdk/bin已經在你的path下了,那麼直接執行jvisualvm,不然須要輸入完整的路徑,如:/usr/bin/jvisualvm

image

4.安裝Visual GC插件

咱們須要安裝Visual GC這個插件,可是java.net這個站點都關了,沒法聯網安裝,因此我寫了另外一篇文章演示如何安裝插件。請移步jvisualvm插件安裝的正確姿式(解決網絡問題):http://www.dubby.cn/detail.html?id=9061

安裝以後就是這個樣子:

image

5.分析Java2Demo

首先雙擊Java2Demo這個本地進程,或者右擊->Open:

image

而後點擊Visual GC這個tab:

image

而後就自由嘗試各個tab頁,看看每一個信息表明JVM的什麼指標。還有,你能夠嘗試着改變Java2Demo上的string和image的數量,看看對垃圾回收有什麼影響。

Java垃圾收集器

如今你知道了垃圾回收的基本概念,還有如何去監視JVM的垃圾回收。如今咱們來了解Java給咱們提供的不一樣的垃圾回收器,還有咱們須要掌握如何使用這些垃圾回收器的命令行。

通用的命令行

這裏給出一些通用的命令行,無論你是什麼收集器,都會用到的。

選項 描述
-Xms 設置JVM啓動時,堆的初始大小
-Xmx 設置堆的最大的容量
-Xmn 設置新生代的容量
-XX:PermSize 設置永久代的初始大小(Java 8以廢棄
-XX:MaxPermSize 設置永久代的最大容量(Java 8以廢棄
-XX:MinHeapFreeRatio 設置堆最小空閒容量,低於這個閾值就擴容,可是堆總量仍是要在Xmx和Xms之間
-XX:MaxHeapFreeRatio 設置堆最大空閒容量,高於這個閾值就收縮,可是堆總量仍是要在Xmx和Xms之間

Serial收集器

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收集器

Parallel收集器在回收新生代時,使用多線程進行收集。默認的回收線程數等於機器的核數。可使用-XX:ParallelGCThreads=<desired number>來設置但願的線程數。

在只有一個CPU的機器上,即使你已經開啓了Parallel收集器,JVM仍是會使用默認的收集器來工做。

使用場景

Parallel收集器也被稱爲吞吐收集器。由於他能夠利用多線程來加快應用的吞吐。這個收集器通常被用做有不少工做須要作,並且對低延要求時不那麼高的應用。例如,批處理(報表,帳單,或者是很大的數據庫查詢等)。

-XX:+UseParallelGC

這個命令行選項是開啓新生代的多線程收集,老年代的多線程收集。老年代也是整理方式。

給個完整的例子:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelGC -jar Java2demo.jar
複製代碼

-XX:+UseParallelOldGC

這個選項是開啓新生代的多線程收集,老年代的多線程收集。老年代也是整理方式。

整理:就是會把活着的對象移到內存的前面,這樣就對象和對象之間的小的空閒的空間(內存碎片)。內存碎片可能致使,空閒空間足夠,可是大對象沒法分配的狀況。

完整的例子:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelOldGC -jar Java2demo.jar
複製代碼

CMS收集器

併發(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
複製代碼

G1收集器

具體的能夠查看【淺度渣文】JVM——G1收集器:http://www.dubby.cn/detail.html?id=9059

這裏簡單描述一下吧,G1在Java 7纔出現的,是一個併發的,低延時的,整理收集器。對堆內存管理和以前的收集器都不同。

命令行選項

開啓命令:

-XX:+UseG1GC
複製代碼

完整的例子:

java -Xmx12m -Xms3m -XX:+UseG1GC -jar Java2demo.jar
複製代碼
相關文章
相關標籤/搜索