jvm的內存分佈,參數配置 和 GC處理機制

轉載html

url: http://blog.csdn.net/ning109314/article/details/10411495java

url:http://www.cnblogs.com/sunada2005/p/3577799.html程序員

url:http://www.open-open.com/lib/view/open1437834571349.html算法

url:http://www.cnblogs.com/redcreen/archive/2011/05/04/2037056.htmlapache

url:http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html數組

url:http://ifeve.com/jvm-internals/緩存

 

1. 什麼是JVM?安全

JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用於計算設備的規範,它是一個虛構出來的計算機,是經過在實際的計算機上仿真模擬各類計算機功能來實現的。Java虛擬機包括一套字節碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法域。 JVM屏蔽了與具體操做系統平臺相關的信息,使Java程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就能夠在多種平臺上不加修改地運行。JVM在執行字節碼時,實際上最終仍是把字節碼解釋成具體平臺上的機器指令執行。服務器

Java語言的一個很是重要的特色就是與平臺的無關性。而使用Java虛擬機是實現這一特色的關鍵。通常的高級語言若是要在不一樣的平臺上運行,至少須要編譯成不一樣的目標代碼。而引入Java語言虛擬機後,Java語言在不一樣平臺上運行時不須要從新編譯。Java語言使用Java虛擬機屏蔽了與具體平臺相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就能夠在多種平臺上不加修改地運行。Java虛擬機在執行字節碼時,把字節碼解釋成具體平臺上的機器指令執行。這就是Java的可以「一次編譯,處處運行」的緣由。數據結構

2. JRE/JDK/JVM是什麼關係?

JRE(JavaRuntimeEnvironment,Java運行環境),也就是Java平臺。全部的Java 程序都要在JRE下才能運行。普通用戶只須要運行已開發好的java程序,安裝JRE便可。
JDK(Java Development Kit)是程序開發者用來來編譯、調試java程序用的開發工具包。JDK的工具也是Java程序,也須要JRE才能運行。爲了保持JDK的獨立性和完整性,在JDK的安裝過程當中,JRE也是 安裝的一部分。因此,在JDK的安裝目錄下有一個名爲jre的目錄,用於存放JRE文件。
JVM(JavaVirtualMachine,Java虛擬機)是JRE的一部分。它是一個虛構出來的計算機,是經過在實際的計算機上仿真模擬各類計算機功能來實現的。JVM有本身完善的硬件架構,如處理器、堆棧、寄存器等,還具備相應的指令系統。Java語言最重要的特色就是跨平臺運行。使用JVM就是爲了支持與操做系統無關,實現跨平臺。

3. JVM原理

JVM是java的核心和基礎,在java編譯器和os平臺之間的虛擬處理器。它是一種利用軟件方法實現的抽象的計算機基於下層的操做系統和硬件平臺,能夠在上面執行java的字節碼程序。
 
java編譯器只要面向JVM,生成JVM能理解的代碼或字節碼文件。Java源文件經編譯成字節碼程序,經過JVM將每一條指令翻譯成不一樣平臺機器碼,經過特定平臺運行。
 
4. JVM執行程序的過程
1) 加載.class文件 2) 管理並分配內存 3) 執行垃圾收集
JRE(java運行時環境)由JVM構造的java程序的運行環,也是Java程序運行的環境,可是他同時一個操做系統的一個應用程序一個進程,所以他也有他本身的運行的生命週期,也有本身的代碼和數據空間。JVM在整個jdk中處於最底層,負責於操做系統的交互,用來屏蔽操做系統環境,提供一個完整的Java運行環境,所以也就虛擬計算機。操做系統裝入JVM是經過jdk中Java.exe來完成,經過下面4步來完成JVM環境:1) 建立JVM裝載環境和配置 2) 裝載JVM.dll 3) 初始化JVM.dll並掛界到JNIENV(JNI調用接口)實例4) 調用JNIEnv實例裝載並處理class類。
 
5. JVM的生命週期
1) JVM實例對應了一個獨立運行的java程序它是進程級別 
a) 啓動。啓動一個Java程序時,一個JVM實例就產生了,任何一個擁有public static void 
main(String[] args)函數的class均可以做爲JVM實例運行的起點 
b) 運行。main()做爲該程序初始線程的起點,任何其餘線程均由該線程啓動。JVM內部有兩種線程:守護線程和非守護線程,main()屬於非守護線程,守護線程一般由JVM本身使用,java程序也能夠代表本身建立的線程是守護線程 
c) 消亡。當程序中的全部非守護線程都終止時,JVM才退出;若安全管理器容許,程序也可使用Runtime類或者System.exit()來退出
 
2) JVM執行引擎實例則對應了屬於用戶運行程序的線程它是線程級別的
 
6. JVM的體系結構
 
vs 中英對照
JVM 相關知識整理和學習
  • 類裝載器(ClassLoader)(用來裝載.class文件)
  • 執行引擎(執行字節碼,或者執行本地方法)
  • 運行時數據區(方法區、堆、java棧、PC寄存器、本地方法棧)

7. JVM運行時數據區

vs.

JVM 相關知識整理和學習JVM 相關知識整理和學習

vs.

JVM_Internal_Architecture_small

vs.

上述貼圖:都在說明JVM 理論中的內存模型

 

第一塊:PC寄存器

PC寄存器是用於存儲每一個線程下一步將執行的JVM指令,如該方法爲native的,則PC寄存器中不存儲任何信息。

第二塊:JVM棧

JVM棧是線程私有的,每一個線程建立的同時都會建立JVM棧,JVM棧中存放的爲當前線程中局部基本類型的變量(java中定義的八種基本類型:boolean、char、byte、short、int、long、float、double)、部分的返回結果以及Stack Frame,非基本類型的對象在JVM棧上僅存放一個指向堆上的地址。

第三塊:堆(Heap)

它是JVM用來存儲對象實例以及數組值的區域,能夠認爲Java中全部經過new建立的對象的內存都在此分配,Heap中的對象的內存須要等待GC進行回收。

(1) 堆是JVM中全部線程共享的,所以在其上進行對象內存的分配均須要進行加鎖,這也致使了new對象的開銷是比較大的

(2) Sun Hotspot JVM爲了提高對象內存分配的效率,對於所建立的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的狀況計算而得,在TLAB上分配對象時不須要加鎖,所以JVM在給線程的對象分配內存時會盡可能的在TLAB上分配,在這種狀況下JVM中分配對象內存的性能和C基本是同樣高效的,但若是對象過大的話則仍然是直接使用堆空間分配

(3) TLAB僅做用於新生代的Eden Space,所以在編寫Java程序時,一般多個小的對象比大的對象分配起來更加高效。

(4) 全部新建立的Object 都將會存儲在新生代Yong Generation中。若是Young Generation的數據在一次或屢次GC後存活下來,那麼將被轉移到OldGeneration。新的Object老是建立在Eden Space。

第四塊:方法區域(Method Area)

(1)在Sun JDK中這塊區域對應的爲PermanetGeneration,又稱爲持久代。

(2)方法區域存放了所加載的類的信息(名稱、修飾符等)、類中的靜態變量、類中定義爲final類型的常量、類中的Field信息、類中的方法信息,當開發人員在程序中經過Class對象中的getName、isInterface等方法來獲取信息時,這些數據都來源於方法區域,同時方法區域也是全局共享的,在必定的條件下它也會被GC,當方法區域須要使用的內存超過其容許的大小時,會拋出OutOfMemory的錯誤信息。

第五塊:運行時常量池(Runtime Constant Pool)

存放的爲類中的固定的常量信息、方法和Field的引用信息等,其空間從方法區域中分配。

第六塊:本地方法堆棧(Native Method Stacks)

JVM採用本地方法堆棧來支持native方法的執行,此區域用於存儲每一個native方法調用的狀態。

8. JVM垃圾回收

GC (Garbage Collection)的基本原理:將內存中再也不被使用的對象進行回收,GC中用於回收的方法稱爲收集器,因爲GC須要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘量的縮短GC對應用形成的暫停

(1)對新生代的對象的收集稱爲minor GC;

(2)對舊生代的對象的收集稱爲Full GC;

(3)程序中主動調用System.gc()強制執行的GC爲Full GC。

不一樣的對象引用類型, GC會採用不一樣的方法進行回收,JVM對象的引用分爲了四種類型:

(1)強引用:默認狀況下,對象採用的均爲強引用(這個對象的實例沒有其餘對象引用,GC時纔會被回收)

(2)軟引用:軟引用是Java中提供的一種比較適合於緩存場景的應用(只有在內存不夠用的狀況下才會被GC)

(3)弱引用:在GC時必定會被GC回收

(4)虛引用:因爲虛引用只是用來得知對象是否被GC

 

內存由 Perm 和 Heap 組成. 其中
Heap = {Old + NEW = { Eden , from, to } }
JVM內存模型中分兩大塊,一塊是 NEW Generation, 另外一塊是Old Generation. 在New Generation中,有一個叫Eden的空間,主要是用來存放新生的對象,還有兩個Survivor Spaces(from,to), 它們用來存放每次垃圾回收後存活下來的對象。在Old Generation中,主要存放應用程序中生命週期長的內存對象,還有個Permanent Generation,主要用來放JVM本身的反射對象,好比類對象和方法對象等。

垃圾回收描述:
在New Generation塊中,垃圾回收通常用Copying的算法,速度快。每次GC的時候,存活下來的對象首先由Eden拷貝到某個Survivor Space, 當Survivor Space空間滿了後, 剩下的live對象就被直接拷貝到Old Generation中去。所以,每次GC後,Eden內存塊會被清空。在Old Generation塊中,垃圾回收通常用mark-compact的算法,速度慢些,但減小內存要求.
垃圾回收分多級,0級爲所有(Full)的垃圾回收,會回收OLD段中的垃圾;1級或以上爲部分垃圾回收,只會回收NEW中的垃圾,內存溢出一般發生於OLD段或Perm段垃圾回收後,仍然無內存空間容納新的Java對象的狀況。
當一個URL被訪問時,內存申請過程以下:
A. JVM會試圖爲相關Java對象在Eden中初始化一塊內存區域
B. 當Eden空間足夠時,內存申請結束。不然到下一步
C. JVM試圖釋放在Eden中全部不活躍的對象(這屬於1或更高級的垃圾回收), 釋放後若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區
D. Survivor區被用來做爲Eden及OLD的中間交換區域,當OLD區空間足夠時,Survivor區的對象會被移到Old區,不然會被保留在Survivor區
E. 當OLD區空間不夠時,JVM會在OLD區進行徹底的垃圾收集(0級)
F. 徹底垃圾收集後,若Survivor及OLD區仍然沒法存放從Eden複製過來的部分對象,致使JVM沒法在Eden區爲新對象建立內存區域,則出現」out of memory錯誤」

JVM調優建議:
ms/mx:定義YOUNG+OLD段的總尺寸,ms爲JVM啓動時YOUNG+OLD的內存大小;mx爲最大可佔用的YOUNG+OLD內存大小。在用戶生產環境上通常將這兩個值設爲相同,以減小運行期間系統在內存申請上所花的開銷。
NewSize/MaxNewSize:定義YOUNG段的尺寸,NewSize爲JVM啓動時YOUNG的內存大小;MaxNewSize爲最大可佔用的YOUNG內存大小。在用戶生產環境上通常將這兩個值設爲相同,以減小運行期間系統在內存申請上所花的開銷。
PermSize/MaxPermSize:定義Perm段的尺寸,PermSize爲JVM啓動時Perm的內存大小;MaxPermSize爲最大可佔用的Perm內存大小。在用戶生產環境上通常將這兩個值設爲相同,以減小運行期間系統在內存申請上所花的開銷。
SurvivorRatio:設置Survivor空間和Eden空間的比例

內存溢出的可能性
1. OLD段溢出
這種內存溢出是最多見的狀況之一,產生的緣由多是:
1) 設置的內存參數太小(ms/mx, NewSize/MaxNewSize)
2) 程序問題
單個程序持續進行消耗內存的處理,如循環幾千次的字符串處理,對字符串處理應建議使用StringBuffer。此時不會報內存溢出錯,卻會使系統持續垃圾收集,沒法處理其它請求,相關問題程序可經過Thread Dump獲取(見系統問題診斷一章)單個程序所申請內存過大,有的程序會申請幾十乃至幾百兆內存,此時JVM也會因沒法申請到資源而出現內存溢出,對此首先要找到相關功能,而後交予程序員修改,要找到相關程序,必須在Apache日誌中尋找。
當Java對象使用完畢後,其所引用的對象卻沒有銷燬,使得JVM認爲他仍是活躍的對象而不進行回收,這樣累計佔用了大量內存而沒法釋放。因爲目前市面上尚未對系統影響小的內存分析工具,故此時只能和程序員一塊兒定位。
2. Perm段溢出
一般因爲Perm段裝載了大量的Servlet類而致使溢出,目前的解決辦法:
1) 將PermSize擴大,通常256M可以知足要求
2) 若別無選擇,則只能將servlet的路徑加到CLASSPATH中,但通常不建議這麼處理

3. C Heap溢出
系統對C Heap沒有限制,故C Heap發生問題時,Java進程所佔內存會持續增加,直到佔用全部可用系統內存
其餘:
JVM有2個GC線程。第一個線程負責回收Heap的Young區。第二個線程在Heap不足時,遍歷Heap,將Young 區升級爲Older區。Older區的大小等於-Xmx減去-Xmn,不能將-Xms的值設的過大,由於第二個線程被迫運行會下降JVM的性能。
爲何一些程序頻繁發生GC?有以下緣由:
l         程序內調用了System.gc()或Runtime.gc()。
l         一些中間件軟件調用本身的GC方法,此時須要設置參數禁止這些GC。
l         Java的Heap過小,通常默認的Heap值都很小。
l         頻繁實例化對象,Release對象。此時儘可能保存並重用對象,例如使用StringBuffer()和String()。
若是你發現每次GC後,Heap的剩餘空間會是總空間的50%,這表示你的Heap處於健康狀態。許多Server端的Java程序每次GC後最好能有65%的剩餘空間。

經驗之談:
1.Server端JVM最好將-Xms和-Xmx設爲相同值。爲了優化GC,最好讓-Xmn值約等於-Xmx的1/3[2]。
2.一個GUI程序最好是每10到20秒間運行一次GC,每次在半秒以內完成[2]。
注意:
1.增長Heap的大小雖然會下降GC的頻率,但也增長了每次GC的時間。而且GC運行時,全部的用戶線程將暫停,也就是GC期間,Java應用程序不作任何工做。
2.Heap大小並不決定進程的內存使用量。進程的內存使用量要大於-Xmx定義的值,由於Java爲其餘任務分配內存,例如每一個線程的Stack等。
2.Stack的設定
每一個線程都有他本身的Stack。

 

-Xss
每一個線程的Stack大小

 

Stack的大小限制着線程的數量。若是Stack過大就好致使內存溢漏。-Xss參數決定Stack大小,例如-Xss1024K。若是Stack過小,也會致使Stack溢漏。
 
3.硬件環境
硬件環境也影響GC的效率,例如機器的種類,內存,swap空間,和CPU的數量。
若是你的程序須要頻繁建立不少transient對象,會致使JVM頻繁GC。這種狀況你能夠增長機器的內存,來減小Swap空間的使用[2]。
 
4.4種GC
第一種爲單線程GC,也是默認的GC。,該GC適用於單CPU機器。
第二種爲Throughput GC,是多線程的GC,適用於多CPU,使用大量線程的程序。第二種GC與第一種GC類似,不一樣在於GC在收集Young區是多線程的,但在Old區和第一種同樣,仍然採用單線程。-XX:+UseParallelGC參數啓動該GC。
第三種爲Concurrent Low Pause GC,相似於第一種,適用於多CPU,並要求縮短因GC形成程序停滯的時間。這種GC能夠在Old區的回收同時,運行應用程序。-XX:+UseConcMarkSweepGC參數啓動該GC。
第四種爲Incremental Low Pause GC,適用於要求縮短因GC形成程序停滯的時間。這種GC能夠在Young區回收的同時,回收一部分Old區對象。-Xincgc參數啓動該GC。
 
JVM 相關知識整理和學習

   3.垃圾回收:   

 JVM中會在如下狀況觸發回收:對象沒有被引用 , 做用域發生未捕捉異常 , 程序正常執行完畢 , 程序執行了System.exit() , 程序發生意外終止。  JVM中標記垃圾使用的算法是一種根搜索算法。簡單的說,就是從一個叫GC Roots的對象開始 , 向下搜索 , 若是一個對象不能達到GC Roots對象的時候 , 說明它能夠被回收了。這種算法比一種叫作引用計數法的垃圾標記算法要好,由於它避免了當兩個對象啊互相引用時沒法被回收的現象。

JVM中對於被標記爲垃圾的對象進行回收時又分爲了一下3種算法:

     1.標記清除算法 ,該算法是從根集合掃描整個空間,標記存活的對象,而後在掃描整個空間對沒有被標記的對象進行回收,這種算法在存活對象較多時比較高效,但會產生內存碎片。

     2.複製算法 ,該算法是從根集合掃描,並將存活的對象複製到新的空間,這種算法在存活對象少時比較高效。

     3.標記整理算法 ,標記整理算法和標記清除算法同樣都會掃描並標記存活對象,在回收未標記對象的同時會整理被標記的對象,解決了內存碎片的問題。

     JVM中,不一樣的 內存區域做用和性質不同,使用的垃圾回收算法也不同,因此JVM中又定義了幾種不一樣的垃圾回收器(上圖中連線表明兩個回收器能夠同時使用)

 

按照基本回收策略分

引用計數(Reference Counting):

比較古老的回收算法。原理是此對象有一個引用,即增長一個計數,刪除一個引用則減小一個計數。垃圾回收時,只用收集計數爲0的對象。此算法最致命的是沒法處理循環引用的問題。 

標記-清除(Mark-Sweep): 

 標記清楚

 

此算法執行分兩階段。第一階段從引用根節點開始標記全部被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法須要暫停整個應用,同時,會產生內存碎片。 

複製(Copying): 

複製 

此算法把內存空間劃爲兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另一個區域中。算法每次只處理正在使用中的對象,所以複製成本比較小,同時複製過去之後還能進行相應的內存整理,不會出現「碎片」問題。固然,此算法的缺點也是很明顯的,就是須要兩倍內存空間。

標記-整理(Mark-Compact):

 標記整理

 此算法結合了「標記-清除」和「複製」兩個算法的優勢。也是分兩階段,第一階段從根節點開始標記全部被引用對象,第二階段遍歷整個堆,把清除未標記對象而且把存活對象「壓縮」到堆的其中一塊,按順序排放。此算法避免了「標記-清除」的碎片問題,同時也避免了「複製」算法的空間問題。

 

按分區對待的方式分

增量收集(Incremental Collecting):實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。不知道什麼緣由JDK5.0中的收集器沒有使用這種算法的。 

分代收集(Generational Collecting):基於對對象生命週期分析後得出的垃圾回收算法。把對象分爲年青代、年老代、持久代,對不一樣生命週期的對象使用不一樣的算法(上述方式中的一個)進行回收。如今的垃圾回收器(從J2SE1.2開始)都是使用此算法的。 

 

按系統線程分

串行收集:串行收集使用單線程處理全部垃圾回收工做,由於無需多線程交互,實現容易,並且效率比較高。可是,其侷限性也比較明顯,即沒法使用多處理器的優點,因此此收集適合單處理器機器。固然,此收集器也能夠用在小數據量(100M左右)狀況下的多處理器機器上。 

並行收集:並行收集使用多線程處理垃圾回收工做,於是速度快,效率高。並且理論上CPU數目越多,越能體現出並行收集器的優點。(串型收集的併發版本,須要暫停jvm) 並行paralise指的是多個任務在多個cpu中一塊兒並行執行,最後將結果合併。效率是N倍。 

併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工做時,須要暫停整個運行環境,而只有垃圾回收程序在運行,所以,系統在垃圾回收時會有明顯的暫停,並且暫停時間會由於堆越大而越長。(和並行收集不一樣,併發只有在開頭和結尾會暫停jvm)併發concurrent指的是多個任務在一個cpu僞同步執行,但實際上是串行調度的,效率並不是直接是N倍。

 

分代垃圾回收

    分代的垃圾回收策略,是基於這樣一個事實:不一樣的對象的生命週期是不同的。所以,不一樣生命週期的對象能夠採起不一樣的收集方式,以便提升回收效率。 

    在Java程序運行的過程當中,會產生大量的對象,其中有些對象是與業務信息相關,好比Http請求中的Session對象、線程、Socket鏈接,這類對象跟業務直接掛鉤,所以生命週期比較長。可是還有一些對象,主要是程序運行過程當中生成的臨時變量,這些對象生命週期會比較短,好比:String對象,因爲其不變類的特性,系統會產生大量的這些對象,有些對象甚至只用一次便可回收。 

    試想,在不進行對象存活時間區分的狀況下,每次垃圾回收都是對整個堆空間進行回收,花費時間相對會長,同時,由於每次回收都須要遍歷全部存活對象,但實際上,對於生命週期長的對象而言,這種遍歷是沒有效果的,由於可能進行了不少次遍歷,可是他們依舊存在。所以,分代垃圾回收採用分治的思想,進行代的劃分,把不一樣生命週期的對象放在不一樣代上,不一樣代上採用最適合它的垃圾回收方式進行回收。 

jvm分代

 如圖所示: 

    虛擬機中的共劃分爲三個代:年輕代(Young Generation)、年老點(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java類的類信息,與垃圾收集要收集的Java對象關係不大。年輕代和年老代的劃分是對垃圾收集影響比較大的。 

年輕代:

    全部新生成的對象首先都是放在年輕代的。年輕代的目標就是儘量快速的收集掉那些生命週期短的對象。年輕代分三個區。一個Eden區,兩個Survivor區(通常而言)。大部分對象在Eden區中生成。當Eden區滿時,還存活的對象將被複制到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活對象將被複制到另一個Survivor區,當這個Survivor區也滿了的時候,從第一個Survivor區複製過來的而且此時還存活的對象,將被複制「年老區(Tenured)」。須要注意,Survivor的兩個區是對稱的,沒前後關係,因此同一個區中可能同時存在從Eden複製過來 對象,和從前一個Survivor複製過來的對象,而複製到年老區的只有從第一個Survivor去過來的對象。並且,Survivor區總有一個是空的。同時,根據程序須要,Survivor區是能夠配置爲多個的(多於兩個),這樣能夠增長對象在年輕代中的存在時間,減小被放到年老代的可能。 

年老代:

    在年輕代中經歷了N次垃圾回收後仍然存活的對象,就會被放到年老代中。所以,能夠認爲年老代中存放的都是一些生命週期較長的對象。 

持久代:

    用於存放靜態文件,現在Java類、方法等。持久代對垃圾回收沒有顯著影響,可是有些應用可能動態生成或者調用一些class,例如Hibernate等,在這種時候須要設置一個比較大的持久代空間來存放這些運行過程當中新增的類。持久代大小經過-XX:MaxPermSize=<N>進行設置。 

 

什麼狀況下觸發垃圾回收 

因爲對象進行了分代處理,所以垃圾回收區域、時間也不同。GC有兩種類型:Scavenge GCFull GC。 

Scavenge GC

    通常狀況下,當新對象生成,而且在Eden申請空間失敗時,就會觸發Scavenge GC,對Eden區域進行GC,清除非存活對象,而且把尚且存活的對象移動到Survivor區。而後整理Survivor的兩個區。這種方式的GC是對年輕代的Eden區進行,不會影響到年老代。由於大部分對象都是從Eden區開始的,同時Eden區不會分配的很大,因此Eden區的GC會頻繁進行。於是,通常在這裏須要使用速度快、效率高的算法,使Eden去能儘快空閒出來。 

Full GC

    對整個堆進行整理,包括Young、Tenured和Perm。Full GC由於須要對整個對進行回收,因此比Scavenge GC要慢,所以應該儘量減小Full GC的次數。在對JVM調優的過程當中,很大一部分工做就是對於FullGC的調節。有以下緣由可能致使Full GC:

· 年老代(Tenured)被寫滿

· 持久代(Perm)被寫滿 

· System.gc()被顯示調用 

·上一次GC以後Heap的各域分配策略動態變化

 圖1 | --> 圖2 | --> 圖3 | --> 圖4 | --> 

分代垃圾回收流程1分代垃圾回收流程2分代垃圾回收流程3分代垃圾回收流程

圖1 --> 圖2 --> 圖3 --> 圖4 -->  

 

1.Serial GC 。從名字上看,串行GC意味着是一種單線程的,因此它要求收集的時候全部的線程暫停。這對於高性能的應用是不合理的,因此串行GC通常用於Client模式的JVM中。2.ParNew GC 。是在SerialGC的基礎上,增長了多線程機制。可是若是機器是單CPU的,這種收集器是比SerialGC效率低的。

3.Parrallel Scavenge GC 。這種收集器又叫吞吐量優先收集器,而吞吐量=程序運行時間/(JVM執行回收的時間+程序運行時間),假設程序運行了100分鐘,JVM的垃圾回收佔用 1分鐘,那麼吞吐量就是99%。Parallel Scavenge GC因爲能夠提供比較不錯的吞吐量,因此被做爲了server模式JVM的默認配置。

4.ParallelOld 是老生代並行收集器的一種,使用了標記整理算法,是JDK1.6中引進的,在以前 老生代 只能使用串行回收收集器。

5.Serial Old 是老生代client模式下的默認收集器,單線程執行,同時也做爲CMS收集器失敗後的備用收集器。

 6.CMS 又稱響應時間優先回收器,使用標記清除算法。他的回收線程數爲(CPU核心數+3)/4,因此當CPU核心數爲2時比較高效些。CMS分爲4個過程:初始標記、併發標記、從新標記、併發清除。

7.GarbageFirst(G1) 。比較特殊的是G1回收器既能夠回收Young Generation,也能夠回收Tenured Generation。它是在JDK6的某個版本中才引入的,性能比較高,同時注意了吞吐量和響應時間。

對於垃圾收集器的組合使用能夠經過下表中的參數指定:

JVM 相關知識整理和學習

默認的GC種類能夠經過jvm.cfg或者經過jmap dump出heap來查看,通常咱們經過jstat -gcutil [pid] 1000能夠查看每秒gc的大致狀況,或者能夠在啓動參數中加入:-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:./gc.log來記錄GC日誌。

 

GC中有一種狀況叫作Full GC,如下幾種狀況會觸發Full GC:

1.Tenured Space空間不足以建立打的對象或者數組,會執行FullGC,而且當FullGC以後空間若是還不夠,那麼會OOM:java heap space。

2.Permanet Generation的大小不足,存放了太多的類信息,在非CMS狀況下回觸發FullGC。若是以後空間還不夠,會OOM:PermGen space。

3.CMS GC時出現promotion failed和concurrent mode failure時,也會觸發FullGC。

   promotion failed是在進行Minor GC時,survivor space放不下、對象只能放入舊生代,而此時舊生代也放不下形成的;

   concurrent mode failure是在執行CMS GC的過程當中同時有對象要放入舊生代,而此時舊生代空間不足形成的。

4.判斷MinorGC後,要晉升到TenuredSpace的對象大小大於TenuredSpace的大小,也會觸發FullGC。能夠看出,當FullGC頻繁發生時,必定是內存出問題了。

young generation有eden、2個survivor 區域組成。其中一個survivor區域一直是空的,是eden區域和另外一個survivor區域在下一次copy collection後活着的objecy的目的地。object在survivo區域被複制直到轉移到tenured區。

咱們要儘可能減小 Full gc 的次數(tenured generation 通常比較大,收集的時間較長,頻繁的Full gc會致使應用的性能收到嚴重的影響)。

堆內存GC
       JVM(採用分代回收的策略),用較高的頻率對年輕的對象(young generation)進行YGC,而對老對象(tenured generation)較少(tenured generation 滿了後才進行)進行Full GC。這樣就不須要每次GC都將內存中全部對象都檢查一遍。

非堆內存不GC

      GC不會在主程序運行期對PermGen Space進行清理,因此若是你的應用中有不少CLASS(特別是動態生成類,固然permgen space存放的內容不只限於類)的話,就極可能出現PermGen Space錯誤。

內存申請、對象衰老過程
1、內存申請過程

    1. JVM會試圖爲相關Java對象在Eden中初始化一塊內存區域;
    2. 當Eden空間足夠時,內存申請結束。不然到下一步;
    3. JVM試圖釋放在Eden中全部不活躍的對象(minor collection),釋放後若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區;
    4. Survivor區被用來做爲Eden及old的中間交換區域,當OLD區空間足夠時,Survivor區的對象會被移到Old區,不然會被保留在Survivor區;
    5. 當old區空間不夠時,JVM會在old區進行major collection;
    6. 徹底垃圾收集後,若Survivor及old區仍然沒法存放從Eden複製過來的部分對象,致使JVM沒法在Eden區爲新對象建立內存區域,則出現"Out of memory錯誤";

 

GC性能方面的考慮

對於GC的性能主要有2個方面的指標:吞吐量throughput(工做時間不算gc的時間佔總的時間比)和暫停pause(gc發生時app對外顯示的沒法響應)。

1. Total Heap

默認狀況下,vm會增長/減小heap大小以維持free space在整個vm中佔的比例,這個比例由MinHeapFreeRatio和MaxHeapFreeRatio指定。

通常而言,server端的app會有如下規則:

  • 對vm分配儘量多的memory;
  • 將Xms和Xmx設爲同樣的值。若是虛擬機啓動時設置使用的內存比較小,這個時候又須要初始化不少對象,虛擬機就必須重複地增長內存。
  • 處理器核數增長,內存也跟着增大。

2. The Young Generation

另一個對於app流暢性運行影響的因素是young generation的大小。young generation越大,minor collection越少;可是在固定heap size狀況下,更大的young generation就意味着小的tenured generation,就意味着更多的major collection(major collection會引起minor collection)。

NewRatio反映的是young和tenured generation的大小比例。NewSize和MaxNewSize反映的是young generation大小的下限和上限,將這兩個值設爲同樣就固定了young generation的大小(同Xms和Xmx設爲同樣)。

 

若是但願,SurvivorRatio也能夠優化survivor的大小,不過這對於性能的影響不是很大。SurvivorRatio是eden和survior大小比例。

通常而言,server端的app會有如下規則:

  • 首先決定能分配給vm的最大的heap size,而後設定最佳的young generation的大小;
  • 若是heap size固定後,增長young generation的大小意味着減少tenured generation大小。讓tenured generation在任什麼時候候夠大,可以容納全部live的data(留10%-20%的空餘)。

經驗&&規則

  1. 年輕代大小選擇
    • 響應時間優先的應用:儘量設大,直到接近系統的最低響應時間限制(根據實際狀況選擇).在此種狀況下,年輕代收集發生的頻率也是最小的.同時,減小到達年老代的對象.
    • 吞吐量優先的應用:儘量的設置大,可能到達Gbit的程度.由於對響應時間沒有要求,垃圾收集能夠並行進行,通常適合8CPU以上的應用.
    • 避免設置太小.當新生代設置太小時會致使:1.YGC次數更加頻繁 2.可能致使YGC對象直接進入舊生代,若是此時舊生代滿了,會觸發FGC.
  2. 年老代大小選擇
    1. 響應時間優先的應用:年老代使用併發收集器,因此其大小須要當心設置,通常要考慮併發會話率和會話持續時間等一些參數.若是堆設置小了,能夠會形成內存碎 片,高回收頻率以及應用暫停而使用傳統的標記清除方式;若是堆大了,則須要較長的收集時間.最優化的方案,通常須要參考如下數據得到:
      併發垃圾收集信息、持久代併發收集次數、傳統GC信息、花在年輕代和年老代回收上的時間比例。
    2. 吞吐量優先的應用:通常吞吐量優先的應用都有一個很大的年輕代和一個較小的年老代.緣由是,這樣能夠儘量回收掉大部分短時間對象,減小中期的對象,而年老代盡存放長期存活對象.
  3. 較小堆引發的碎片問題
    由於年老代的併發收集器使用標記,清除算法,因此不會對堆進行壓縮.當收集器回收時,他會把相鄰的空間進行合併,這樣能夠分配給較大的對象.可是,當堆空間較小時,運行一段時間之後,就會出現"碎片",若是併發收集器找不到足夠的空間,那麼併發收集器將會中止,而後使用傳統的標記,清除方式進行回收.若是出現"碎片",可能須要進行以下配置:
    -XX:+UseCMSCompactAtFullCollection:使用併發收集器時,開啓對年老代的壓縮.
    -XX:CMSFullGCsBeforeCompaction=0:上面配置開啓的狀況下,這裏設置多少次Full GC後,對年老代進行壓縮
  4. 用64位操做系統,Linux下64位的jdk比32位jdk要慢一些,可是吃得內存更多,吞吐量更大
  5. XMX和XMS設置同樣大,MaxPermSize和MinPermSize設置同樣大,這樣能夠減輕伸縮堆大小帶來的壓力
  6. 使用CMS的好處是用盡可能少的新生代,經驗值是128M-256M, 而後老生代利用CMS並行收集, 這樣能保證系統低延遲的吞吐效率。 實際上cms的收集停頓時間很是的短,2G的內存, 大約20-80ms的應用程序停頓時間
  7. 系統停頓的時候多是GC的問題也多是程序的問題,多用jmap和jstack查看,或者killall -3 java,而後查看java控制檯日誌,能看出不少問題。(相關工具的使用方法將在後面的blog中介紹)
  8. 仔細瞭解本身的應用,若是用了緩存,那麼年老代應該大一些,緩存的HashMap不該該無限制長,建議採用LRU算法的Map作緩存,LRUMap的最大長度也要根據實際狀況設定。
  9. 採用併發回收時,年輕代小一點,年老代要大,由於年老大用的是併發回收,即便時間長點也不會影響其餘程序繼續運行,網站不會停頓
  10. JVM參數的設置(特別是 –Xmx –Xms –Xmn -XX:SurvivorRatio  -XX:MaxTenuringThreshold等參數的設置沒有一個固定的公式,須要根據PV old區實際數據 YGC次數等多方面來衡量。爲了不promotion faild可能會致使xmn設置偏小,也意味着YGC的次數會增多,處理併發訪問的能力降低等問題。每一個參數的調整都須要通過詳細的性能測試,才能找到特定應用的最佳配置。

promotion failed:

垃圾回收時promotion failed是個很頭痛的問題,通常多是兩種緣由產生,第一個緣由是救助空間不夠,救助空間裏的對象還不該該被移動到年老代,但年輕代又有不少對象須要放入救助空間;第二個緣由是年老代沒有足夠的空間接納來自年輕代的對象;這兩種狀況都會轉向Full GC,網站停頓時間較長。

解決方方案一:

第一個緣由個人最終解決辦法是去掉救助空間,設置-XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0便可,第二個緣由個人解決辦法是設置CMSInitiatingOccupancyFraction爲某個值(假設70),這樣年老代空間到70%時就開始執行CMS,年老代有足夠的空間接納來自年輕代的對象。

解決方案一的改進方案:

又有改進了,上面方法不太好,由於沒有用到救助空間,因此年老代容易滿,CMS執行會比較頻繁。我改善了一下,仍是用救助空間,可是把救助空間加大,這樣也不會有promotion failed。具體操做上,32位Linux和64位Linux好像不同,64位系統彷佛只要配置MaxTenuringThreshold參數,CMS仍是有暫停。爲了解決暫停問題和promotion failed問題,最後我設置-XX:SurvivorRatio=1 ,並把MaxTenuringThreshold去掉,這樣即沒有暫停又不會有promotoin failed,並且更重要的是,年老代和永久代上升很是慢(由於好多對象到不了年老代就被回收了),因此CMS執行頻率很是低,好幾個小時才執行一次,這樣,服務器都不用重啓了。

 

JVM參數的含義 實例見實例分析

參數名稱 含義 默認值  
-Xms 初始堆大小 物理內存的1/64(<1GB) 默認(MinHeapFreeRatio參數能夠調整)空餘堆內存小於40%時,JVM就會增大堆直到-Xmx的最大限制.
-Xmx 最大堆大小 物理內存的1/4(<1GB) 默認(MaxHeapFreeRatio參數能夠調整)空餘堆內存大於70%時,JVM會減小堆直到 -Xms的最小限制
-Xmn 年輕代大小(1.4or lator)   注意:此處的大小是(eden+ 2 survivor space).與jmap -heap中顯示的New gen是不一樣的。
整個堆大小=年輕代大小 + 年老代大小 + 持久代大小.
增大年輕代後,將會減少年老代大小.此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8
-XX:NewSize 設置年輕代大小(for 1.3/1.4)    
-XX:MaxNewSize 年輕代最大值(for 1.3/1.4)    
-XX:PermSize 設置持久代(perm gen)初始值 物理內存的1/64  
-XX:MaxPermSize 設置持久代最大值 物理內存的1/4  
-Xss 每一個線程的堆棧大小   JDK5.0之後每一個線程堆棧大小爲1M,之前每一個線程堆棧大小爲256K.更具應用的線程所需內存大小進行 調整.在相同物理內存下,減少這個值能生成更多的線程.可是操做系統對一個進程內的線程數仍是有限制的,不能無限生成,經驗值在3000~5000左右
通常小的應用, 若是棧不是很深, 應該是128k夠用的 大的應用建議使用256k。這個選項對性能影響比較大,須要嚴格的測試。(校長)
和threadstacksize選項解釋很相似,官方文檔彷佛沒有解釋,在論壇中有這樣一句話:"」
-Xss is translated in a VM flag named ThreadStackSize」
通常設置這個值就能夠了。
-XX:ThreadStackSize Thread Stack Size   (0 means use default stack size) [Sparc: 512; Solaris x86: 320 (was 256 prior in 5.0 and earlier); Sparc 64 bit: 1024; Linux amd64: 1024 (was 0 in 5.0 and earlier); all others 0.]
-XX:NewRatio 年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)   -XX:NewRatio=4表示年輕代與年老代所佔比值爲1:4,年輕代佔整個堆棧的1/5
Xms=Xmx而且設置了Xmn的狀況下,該參數不須要進行設置。
-XX:SurvivorRatio Eden區與Survivor區的大小比值   設置爲8,則兩個Survivor區與一個Eden區的比值爲2:8,一個Survivor區佔整個年輕代的1/10
-XX:LargePageSizeInBytes 內存頁的大小不可設置過大, 會影響Perm的大小   =128m
-XX:+UseFastAccessorMethods 原始類型的快速優化    
-XX:+DisableExplicitGC 關閉System.gc()   這個參數須要嚴格的測試
-XX:MaxTenuringThreshold 垃圾最大年齡   若是設置爲0的話,則年輕代對象不通過Survivor區,直接進入年老代. 對於年老代比較多的應用,能夠提升效率.若是將此值設置爲一個較大值,則年輕代對象會在Survivor區進行屢次複製,這樣能夠增長對象再年輕代的存活 時間,增長在年輕代即被回收的機率
該參數只有在串行GC時纔有效.
-XX:+AggressiveOpts 加快編譯    
-XX:+UseBiasedLocking 鎖機制的性能改善    
-Xnoclassgc 禁用垃圾回收    
-XX:SoftRefLRUPolicyMSPerMB 每兆堆空閒空間中SoftReference的存活時間 1s softly reachable objects will remain alive for some amount of time after the last time they were referenced. The default value is one second of lifetime per free megabyte in the heap
-XX:PretenureSizeThreshold 對象超過多大是直接在舊生代分配 0 單位字節 新生代採用Parallel Scavenge GC時無效
另外一種直接在舊生代分配的狀況是大的數組對象,且數組中無外部引用對象.
-XX:TLABWasteTargetPercent TLAB佔eden區的百分比 1%  
-XX:+CollectGen0First FullGC時是否先YGC false  

並行收集器相關參數

-XX:+UseParallelGC Full GC採用parallel MSC
(此項待驗證)
 

選擇垃圾收集器爲並行收集器.此配置僅對年輕代有效.即上述配置下,年輕代使用併發收集,而年老代仍舊使用串行收集.(此項待驗證)

-XX:+UseParNewGC 設置年輕代爲並行收集   可與CMS收集同時使用
JDK5.0以上,JVM會根據系統配置自行設置,因此無需再設置此值
-XX:ParallelGCThreads 並行收集器的線程數   此值最好配置與處理器數目相等 一樣適用於CMS
-XX:+UseParallelOldGC 年老代垃圾收集方式爲並行收集(Parallel Compacting)   這個是JAVA 6出現的參數選項
-XX:MaxGCPauseMillis 每次年輕代垃圾回收的最長時間(最大暫停時間)   若是沒法知足此時間,JVM會自動調全年輕代大小,以知足此值.
-XX:+UseAdaptiveSizePolicy 自動選擇年輕代區大小和相應的Survivor區比例   設置此選項後,並行收集器會自動選擇年輕代區大小和相應的Survivor區比例,以達到目標系統規定的最低相應時間或者收集頻率等,此值建議使用並行收集器時,一直打開.
-XX:GCTimeRatio 設置垃圾回收時間佔程序運行時間的百分比   公式爲1/(1+n)
-XX:+ScavengeBeforeFullGC Full GC前調用YGC true Do young generation GC prior to a full GC. (Introduced in 1.4.1.)

CMS相關參數

-XX:+UseConcMarkSweepGC 使用CMS內存收集   測試中配置這個之後,-XX:NewRatio=4的配置失效了,緣由不明.因此,此時年輕代大小最好用-Xmn設置.???
-XX:+AggressiveHeap     試圖是使用大量的物理內存
長時間大內存使用的優化,能檢查計算資源(內存, 處理器數量)
至少須要256MB內存
大量的CPU/內存, (在1.4.1在4CPU的機器上已經顯示有提高)
-XX:CMSFullGCsBeforeCompaction 多少次後進行內存壓縮   因爲併發收集器不對內存空間進行壓縮,整理,因此運行一段時間之後會產生"碎片",使得運行效率下降.此值設置運行多少次GC之後對內存空間進行壓縮,整理.
-XX:+CMSParallelRemarkEnabled 下降標記停頓    
-XX+UseCMSCompactAtFullCollection 在FULL GC的時候, 對年老代的壓縮   CMS是不會移動內存的, 所以, 這個很是容易產生碎片, 致使內存不夠用, 所以, 內存的壓縮這個時候就會被啓用。 增長這個參數是個好習慣。
可能會影響性能,可是能夠消除碎片
-XX:+UseCMSInitiatingOccupancyOnly 使用手動定義初始化定義開始CMS收集   禁止hostspot自行觸發CMS GC
-XX:CMSInitiatingOccupancyFraction=70 使用cms做爲垃圾回收
使用70%後開始CMS收集
92 爲了保證不出現promotion failed(見下面介紹)錯誤,該值的設置須要知足如下公式CMSInitiatingOccupancyFraction計算公式
-XX:CMSInitiatingPermOccupancyFraction 設置Perm Gen使用到達多少比率時觸發 92  
-XX:+CMSIncrementalMode 設置爲增量模式   用於單CPU狀況
-XX:+CMSClassUnloadingEnabled      

輔助信息

-XX:+PrintGC    

輸出形式:

[GC 118250K->113543K(130112K), 0.0094143 secs]
[Full GC 121376K->10414K(130112K), 0.0650971 secs]

-XX:+PrintGCDetails    

輸出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]
[GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]

-XX:+PrintGCTimeStamps      
-XX:+PrintGC:PrintGCTimeStamps     可與-XX:+PrintGC -XX:+PrintGCDetails混合使用
輸出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
-XX:+PrintGCApplicationStoppedTime 打印垃圾回收期間程序暫停的時間.可與上面混合使用   輸出形式:Total time for which application threads were stopped: 0.0468229 seconds
-XX:+PrintGCApplicationConcurrentTime 打印每次垃圾回收前,程序未中斷的執行時間.可與上面混合使用   輸出形式:Application time: 0.5291524 seconds
-XX:+PrintHeapAtGC 打印GC先後的詳細堆棧信息    
-Xloggc:filename 把相關日誌信息記錄到文件以便分析.
與上面幾個配合使用
   

-XX:+PrintClassHistogram

garbage collects before printing the histogram.    
-XX:+PrintTLAB 查看TLAB空間的使用狀況    
XX:+PrintTenuringDistribution 查看每次minor GC後新的存活週期的閾值  

Desired survivor size 1048576 bytes, new threshold 7 (max 15)
new threshold 7即標識新的存活週期的閾值爲7。

GC

 

= G1 ===================================

 

 

傳說中的G1,傳說中的low-pause垃圾收集。Java SE 6的update14版本中已經包含測試版,能夠在啓動時加JVM參數來啓用

-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC

 

http://www.blogjava.net/BlueDavy/archive/2009/03/11/259230.html

本文摘自《構建高性能的大型分佈式Java應用》一書,Garbage First簡稱G1,它的目標是要作到儘可能減小GC所致使的應用暫停的時間,讓應用達到準實時的效果,同時保持JVM堆空間的利用率,將做爲CMS的替代者在JDK 7中閃亮登場,其最大的特點在於容許指定在某個時間段內GC所致使的應用暫停的時間最大爲多少,例如在100秒內最多容許GC致使的應用暫停時間爲1秒,這個特性對於準實時響應的系統而言很是的吸引人,這樣就不再用擔憂系統忽然會暫停個兩三秒了。

 

G1要作到這樣的效果,也是有前提的,一方面是硬件環境的要求,必須是多核的CPU以及較大的內存(從規範來看,512M以上就知足條件了),另一方面是須要接受吞吐量的稍微下降,對於實時性要求高的系統而言,這點應該是能夠接受的。

爲了可以達到這樣的效果,G1在原有的各類GC策略上進行了吸取和改進,在G1中能夠看到增量收集器和CMS的影子,但它不只僅是吸取原有GC策略的優勢,並在此基礎上作出了不少的改進,簡單來講,G1吸取了增量GC以及CMS的精髓,將整個jvm Heap劃分爲多個固定大小的region,掃描時採用Snapshot-at-the-beginning的併發marking算法(具體在後面內容詳細解釋)對整個heap中的region進行mark,回收時根據region中活躍對象的bytes進行排序,首先回收活躍對象bytes小以及回收耗時短(預估出來的時間)的region,回收的方法爲將此region中的活躍對象複製到另外的region中,根據指定的GC所能佔用的時間來估算能回收多少region,這點和之前版本的Full GC時得處理整個heap很是不一樣,這樣就作到了可以儘可能短期的暫停應用,又能回收內存,因爲這種策略在回收時首先回收的是垃圾對象所佔空間最多的region,所以稱爲Garbage First。

看完上面對於G1策略的簡短描述,並不能清楚的掌握G1,在繼續詳細看G1的步驟以前,必須先明白G1對於JVM Heap的改造,這些對於習慣了劃分爲new generation、old generation的你們來講都有很多的新意。

G1將Heap劃分爲多個固定大小的region,這也是G1可以實現控制GC致使的應用暫停時間的前提,region之間的對象引用經過remembered set來維護,每一個region都有一個remembered set,remembered set中包含了引用當前region中對象的region的對象的pointer,因爲同時應用也會形成這些region中對象的引用關係不斷的發生改變,G1採用了Card Table來用於應用通知region修改remembered sets,Card Table由多個512字節的Card構成,這些Card在Card Table中以1個字節來標識,每一個應用的線程都有一個關聯的remembered set log,用於緩存和順序化線程運行時形成的對於card的修改,另外,還有一個全局的filled RS buffers,當應用線程執行時修改了card後,若是形成的改變僅爲同一region中的對象之間的關聯,則不記錄remembered set log,如形成的改變爲跨region中的對象的關聯,則記錄到線程的remembered set log,如線程的remembered set log滿了,則放入全局的filled RS buffers中,線程自身則從新建立一個新的remembered set log,remembered set自己也是一個由一堆cards構成的哈希表。

儘管G1將Heap劃分爲了多個region,但其默認採用的仍然是分代的方式,只是僅簡單的劃分爲了年輕代(young)和非年輕代,這也是因爲G1仍然堅信大多數新建立的對象都是不須要長的生命週期的,對於應用新建立的對象,G1將其放入標識爲young的region中,對於這些region,並不記錄remembered set logs,掃描時只需掃描活躍的對象,G1在分代的方式上還可更細的劃分爲:fully young或partially young,fully young方式暫停的時候僅處理young regions,partially一樣處理全部的young regions,但它還會根據容許的GC的暫停時間來決定是否要加入其餘的非young regions,G1是運行到fully-young方式仍是partially young方式,外部是不能決定的,在啓動時,G1採用的爲fully-young方式,當G1完成一次Concurrent Marking後,則切換爲partially young方式,隨後G1跟蹤每次回收的效率,若是回收fully-young中的regions已經能夠知足內存須要的話,那麼就切換回fully young方式,但當heap size的大小接近滿的狀況下,G1會切換到partially young方式,以保證能提供足夠的內存空間給應用使用。

除了分代方式的劃分外,G1還支持另一種pure G1的方式,也就是不進行代的劃分,pure方式和分代方式的具體不一樣在下面的具體執行步驟中進行描述。

掌握了這些概念後,繼續來看G1的具體執行步驟:

1.         Initial Marking

G1對於每一個region都保存了兩個標識用的bitmap,一個爲previous marking bitmap,一個爲next marking bitmap,bitmap中包含了一個bit的地址信息來指向對象的起始點。

開始Initial Marking以前,首先併發的清空next marking bitmap,而後中止全部應用線程,並掃描標識出每一個region中root可直接訪問到的對象,將region中top的值放入next top at mark start(TAMS)中,以後恢復全部應用線程。

觸發這個步驟執行的條件爲:

l  G1定義了一個JVM Heap大小的百分比的閥值,稱爲h,另外還有一個H,H的值爲(1-h)*Heap Size,目前這個h的值是固定的,後續G1也許會將其改成動態的,根據jvm的運行狀況來動態的調整,在分代方式下,G1還定義了一個u以及soft limit,soft limit的值爲H-u*Heap Size,當Heap中使用的內存超過了soft limit值時,就會在一次clean up執行完畢後在應用容許的GC暫停時間範圍內儘快的執行此步驟;

l  在pure方式下,G1將marking與clean up組成一個環,以便clean up能充分的使用marking的信息,當clean up開始回收時,首先回收可以帶來最多內存空間的regions,當通過屢次的clean up,回收到沒多少空間的regions時,G1從新初始化一個新的marking與clean up構成的環。

2.         Concurrent Marking

按照以前Initial Marking掃描到的對象進行遍歷,以識別這些對象的下層對象的活躍狀態,對於在此期間應用線程併發修改的對象的以來關係則記錄到remembered set logs中,新建立的對象則放入比top值更高的地址區間中,這些新建立的對象默認狀態即爲活躍的,同時修改top值。

3.         Final Marking Pause

當應用線程的remembered set logs未滿時,是不會放入filled RS buffers中的,在這樣的狀況下,這些remebered set logs中記錄的card的修改就會被更新了,所以須要這一步,這一步要作的就是把應用線程中存在的remembered set logs的內容進行處理,並相應的修改remembered sets,這一步須要暫停應用,並行的運行。

4.         Live Data Counting and Cleanup

值得注意的是,在G1中,並非說Final Marking Pause執行完了,就確定執行Cleanup這步的,因爲這步須要暫停應用,G1爲了可以達到準實時的要求,須要根據用戶指定的最大的GC形成的暫停時間來合理的規劃何時執行Cleanup,另外還有幾種狀況也是會觸發這個步驟的執行的:

l  G1採用的是複製方法來進行收集,必須保證每次的」to space」的空間都是夠的,所以G1採起的策略是當已經使用的內存空間達到了H時,就執行Cleanup這個步驟;

l  對於full-young和partially-young的分代模式的G1而言,則還有狀況會觸發Cleanup的執行,full-young模式下,G1根據應用可接受的暫停時間、回收young regions須要消耗的時間來估算出一個yound regions的數量值,當JVM中分配對象的young regions的數量達到此值時,Cleanup就會執行;partially-young模式下,則會盡可能頻繁的在應用可接受的暫停時間範圍內執行Cleanup,並最大限度的去執行non-young regions的Cleanup。

這一步中GC線程並行的掃描全部region,計算每一個region中低於next TAMS值中marked data的大小,而後根據應用所指望的GC的短延時以及G1對於region回收所需的耗時的預估,排序region,將其中活躍的對象複製到其餘region中。

 

G1爲了可以儘可能的作到準實時的響應,例如估算暫停時間的算法、對於常常被引用的對象的特殊處理等,G1爲了可以讓GC既可以充分的回收內存,又可以儘可能少的致使應用的暫停,可謂費盡心思,從G1的論文中的性能評測來看效果也是不錯的,不過若是G1能容許開發人員在編寫代碼時指定哪些對象是不用mark的就更完美了,這對於有巨大緩存的應用而言,會有很大的幫助,G1將隨JDK 6 Update 14 beta發佈。

 

 

= CMS ==================================

 

http://www.iteye.com/topic/1119491

 

1.整體介紹:

 

CMS(Concurrent Mark-Sweep)是以犧牲吞吐量爲代價來得到最短回收停頓時間的垃圾回收器。併發意味着除了開頭和結束階段,須要暫停JVM,其它時間gc和應用一塊兒執行。對於要求服務器響應速度的應用上,這種垃圾回收器很是適合。在啓動JVM參數加上-XX:+UseConcMarkSweepGC ,這個參數表示對於老年代的回收採用CMS。CMS採用的基礎算法是:標記—清除。默認會開啓 -XX :+UseParNewGC,在年輕代使用並行複製收集。

2.CMS過程:

  • 初始標記(STW initial mark)
  • 併發標記(Concurrent marking)
  • 併發預清理(Concurrent precleaning)
  • 從新標記(STW remark)
  • 併發清理(Concurrent sweeping)
  • 併發重置(Concurrent reset)

初始標記 :在這個階段,須要虛擬機停頓正在執行的任務,官方的叫法STW(Stop The Word)。這個過程從垃圾回收的"根對象"開始,只掃描到可以和"根對象"直接關聯的對象,並做標記。因此這個過程雖然暫停了整個JVM,可是很快就完成了。

併發標記 :這個階段緊隨初始標記階段,在初始標記的基礎上繼續向下追溯標記。併發標記階段,應用程序的線程和併發標記的線程併發執行,因此用戶不會感覺到停頓。

併發預清理 :併發預清理階段仍然是併發的。在這個階段,虛擬機查找在執行併發標記階段新進入老年代的對象(可能會有一些對象重新生代晉升到老年代, 或者有一些對象被分配到老年代)。經過從新掃描,減小下一個階段"從新標記"的工做,由於下一個階段會Stop The World。

從新標記 :這個階段會暫停虛擬機,收集器線程掃描在CMS堆中剩餘的對象。掃描從"跟對象"開始向下追溯,並處理對象關聯。

併發清理 :清理垃圾對象,這個階段收集器線程和應用程序線程併發執行。

併發重置 :這個階段,重置CMS收集器的數據結構,等待下一次垃圾回收。

 

CSM執行過程: 

3.CMS缺點

  • CMS回收器採用的基礎算法是Mark-Sweep。全部CMS不會整理、壓縮堆空間。這樣就會有一個問題:通過CMS收集的堆會產生空間碎片。 CMS不對堆空間整理壓縮節約了垃圾回收的停頓時間,但也帶來的堆空間的浪費。爲了解決堆空間浪費問題,CMS回收器再也不採用簡單的指針指向一塊可用堆空 間來爲下次對象分配使用。而是把一些未分配的空間彙總成一個列表,當JVM分配對象空間的時候,會搜索這個列表找到足夠大的空間來hold住這個對象。
  • 須要更多的CPU資源。從上面的圖能夠看到,爲了讓應用程序不停頓,CMS線程和應用程序線程併發執行,這樣就須要有更多的CPU,單純靠線程切 換是不靠譜的。而且,從新標記階段,爲空保證STW快速完成,也要用到更多的甚至全部的CPU資源。固然,多核多CPU也是將來的趨勢!
  • CMS的另外一個缺點是它須要更大的堆空間。由於CMS標記階段應用程序的線程仍是在執行的,那麼就會有堆空間繼續分配的狀況,爲了保證在CMS回 收完堆以前還有空間分配給正在運行的應用程序,必須預留一部分空間。也就是說,CMS不會在老年代滿的時候纔開始收集。相反,它會嘗試更早的開始收集,已 避免上面提到的狀況:在回收完成以前,堆沒有足夠空間分配!默認當老年代使用68%的時候,CMS就開始行動了。 – XX:CMSInitiatingOccupancyFraction =n 來設置這個閥值。

總得來講,CMS回收器減小了回收的停頓時間,可是下降了堆空間的利用率。

4.啥時候用CMS

若是你的應用程序對停頓比較敏感,而且在應用程序運行的時候能夠提供更大的內存和更多的CPU(也就是硬件牛逼),那麼使用CMS來收集會給你帶來好處。還有,若是在JVM中,有相對較多存活時間較長的對象(老年代比較大)會更適合使用CMS。

 

 

 

= 調試工具 ==================================

 

jmap

 

jmap -heap pid  (不能觀察G1模式)

 

using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC

Heap Configuration:
   MinHeapFreeRatio = 40
   MaxHeapFreeRatio = 70
   MaxHeapSize      = 2147483648 (2048.0MB)
   NewSize          = 268435456 (256.0MB)
   MaxNewSize       = 268435456 (256.0MB)
   OldSize          = 805306368 (768.0MB)
   NewRatio         = 7
   SurvivorRatio    = 8
   PermSize         = 134217728 (128.0MB)
   MaxPermSize      = 134217728 (128.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 241631232 (230.4375MB)
   used     = 145793088 (139.03912353515625MB)
   free     = 95838144 (91.39837646484375MB)
   60.33702133340114% used
Eden Space:
   capacity = 214827008 (204.875MB)
   used     = 132689456 (126.54252624511719MB)
   free     = 82137552 (78.33247375488281MB)
   61.7657236095752% used
From Space:
   capacity = 26804224 (25.5625MB)
   used     = 13103632 (12.496597290039062MB)
   free     = 13700592 (13.065902709960938MB)
   48.886444166411984% used
To Space:
   capacity = 26804224 (25.5625MB)
   used     = 0 (0.0MB)
   free     = 26804224 (25.5625MB)
   0.0% used
concurrent mark-sweep generation: (old區)
   capacity = 1879048192 (1792.0MB)
   used     = 1360638440 (1297.6059341430664MB)
   free     = 518409752 (494.3940658569336MB)
   72.41104543209076% used
Perm Generation:
   capacity = 134217728 (128.0MB)
   used     = 65435064 (62.40373992919922MB)
   free     = 68782664 (65.59626007080078MB)
   48.75292181968689% used

 

 

jmap -histo:live pid

 

num     #instances         #bytes  class name
----------------------------------------------
   1:       3148147      209172848  [B
   2:       2584345      144723320  java.lang.ref.SoftReference
   3:       2578827      123783696  sun.misc.CacheEntry
   4:        781560      112544640  com.sun.net.ssl.internal.ssl.SSLSessionImpl
   5:       1385200       89970592  [C
   6:        783287       87807200  [Ljava.util.Hashtable$Entry;
   7:       1421399       56855960  java.lang.String
   8:            12       56828880  [Lsun.misc.CacheEntry;
   9:       2343358       56240592  com.sun.net.ssl.internal.ssl.SessionId
  10:        783185       50123840  java.util.Hashtable
  11:        783094       50118016  java.lang.ref.Finalizer
  12:        287243       36086720  [Ljava.lang.Object;
  13:        263376       33712128  org.apache.commons.pool.impl.GenericObjectPool

 

 

jstat

 

jstat -gccause 31169 60000 1000

 

(sweep 1,2) (Eden) (Old) (Perm) (Young GC, GCTime)(Full GC, GCTime)

  S0         S1     E         O         P     YGC     YGCT     FGC    FGCT      GCT             LGCC                 GCC                 
 48.80   0.00  68.94  69.55  48.86  30202  725.319 51835 5083.298 5808.616 unknown GCCause      No GC               
 47.98   0.00  37.47  69.61  48.86  30206  725.385 51835 5083.298 5808.682 unknown GCCause      No GC               
 50.73   0.00  51.72  69.65  48.86  30210  725.459 51835 5083.298 5808.757 unknown GCCause      No GC               
  0.00  50.02  82.67  69.60  48.84  30213  725.508 51836 5091.572 5817.081 unknown GCCause      No GC               

 

jstat -gcutil $pid

 

  S0         S1     E         O       P       YGC     YGCT    FGC    FGCT     GCT   
 74.79   0.00  95.15   0.86  37.35      2        0.112     0        0.000      0.112

 

O = old occupied

YGC = young gc time ( new part )

YGCT = young gc total cost time

FGC = full gc time ( old part )

FGCT = full gc total cost time

GCT = all gc cost time

 

 

jvisualvm

window下啓動遠程監控,並在被監控服務端,啓動jstatd服務。

 

建立安全策略文件,並命名爲jstatd.all.policy
grant codebase "file:${java.home}/../lib/tools.jar" {
    permission java.security.AllPermission;
};

jstatd -J-Djava.security.policy=jstatd.all.policy -p 8080 &


======================== Tunning =================
 
典型配置:
-server -Xmx2g -Xms2g -Xmn512m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true
 
 
 
+AggressiveOpts 激進優化,默認開啓,使用java新特性優化
 
1. 默認使用串行收集器, 單個cpu時適用
 
2. 吞吐收集器(throughput collector):命令行參數:-XX:+UseParallelGC。在新生代使用並行清除收集策略,在舊生代和默認收集器相同。
適用:a、擁有2個以上cpu, b、臨時對象較多的程序
-XX:ParallelGCThreads 並行收集線程數量,最好和cpu數量至關

3. 併發收集器(concurrent low pause collector):命令行參數:-XX:+UseConcMarkSweepGC。在舊生代使用併發收集策略,大部分收集工做都是和應用併發進行的,在進行收集的時候,應用的暫停時間很短。默認配套打開 -XX:+UseParNewGC,會在新生代使用並行複製收集。
適用:a、擁有多個cpu, b、老對象較多的程序
 
若是使用了UseParNewGC,那麼同時使用CMSParallelRemarkEnabled參數能夠下降標識暫停
 
-XX:+UseCMSCompactAtFullCollection:打開對年老代的壓縮。可能會影響性能,可是能夠消除碎片
-XX:+UseFastAccessorMethods 原始類型的快速優化
-XX:SurvivorRatio 新生區中,eden&survivor的比例,設置爲8
-XX:TargetSurvivorRatio 生存區須要作垃圾回收的比例值,默認爲50%,設置高些能夠更好的利用該區
 
 
各個垃圾收集器之間的區別:
 
 
新生代,單獨區域單獨收集,不會影響老生代,由於區域小,且容許漏收集,採用複製清除的方法,更快。
 
 

The (original) copying collector (Enabled by default). When this collector kicks in, all application threads are stopped, and the copying collection proceeds using one thread (which means only one CPU even if on a multi-CPU machine). This is known as a stop-the-world collection, because basically the JVM pauses everything else until the collection is completed.

The parallel copying collector (Enabled using -XX:+UseParNewGC). Like the original copying collector, this is a stop-the-world collector. However this collector parallelizes the copying collection over multiple threads, which is more efficient than the original single-thread copying collector for multi-CPU machines (though not for single-CPU machines). This algorithm potentially speeds up young generation collection by a factor equal to the number of CPUs available, when compared to the original singly-threaded copying collector.

The parallel scavenge collector (Enabled using -XX:UseParallelGC). This is like the previous parallel copying collector, but the algorithm is tuned for gigabyte heaps (over 10GB) on multi-CPU machines. This collection algorithm is designed to maximize throughput while minimizing pauses. It has an optional adaptive tuning policy which will automatically resize heap spaces. If you use this collector, you can only use the the original mark-sweep collector in the old generation (i.e. the newer old generation concurrent collector cannot work with this young generation collector).

 

UserParallelGC使用了更高效的算法,用於處理大規模內存>10G場景,提供了大吞吐量功能。可是,同時在老生代,只能使用串行的標記清除方法。

 

老生代,必須作fullgc,必須從root開始全面標識收集。

 

  • The (original) mark-sweep collector (Enabled by default). This uses a stop-the-world mark-and-sweep collection algorithm. The collector is single-threaded, the entire JVM is paused and the collector uses only one CPU until completed.
  • The concurrent collector (Enabled using -XX:+UseConcMarkSweepGC). This collector tries to allow application processing to continue as much as possible during the collection. Splitting the collection into six phases described shortly, four are concurrent while two are stop-the-world:
    1. the initial-mark phase (stop-the-world, snapshot the old generation so that we can run most of the rest of the collection concurrent to the application threads);
    2. the mark phase (concurrent, mark the live objects traversing the object graph from the roots);
    3. the pre-cleaning phase (concurrent);
    4. the re-mark phase (stop-the-world, another snapshot to capture any changes to live objects since the collection started);
    5. the sweep phase (concurrent, recycles memory by clearing unreferenced objects);
    6. the reset phase (concurrent).
    If "the rate of creation" of objects is too high, and the concurrent collector is not able to keep up with the concurrent collection, it falls back to the traditional mark-sweep collector.
  • The incremental collector (Enabled using -Xincgc). The incremental collector uses a "train" algorithm to collect small portions of the old generation at a time. This collector has higher overheads than the mark-sweep collector, but because small numbers of objects are collected each time, the (stop-the-world) garbage collection pause is minimized at the cost of total garbage collection taking longer. The "train" algorithm does not guarantee a maximum pause time, but pause times are typically less than ten milliseconds.
相關文章
相關標籤/搜索