JVM記憶體組成&調優引數詳解

JVM記憶體組成&調優引數詳解
總記憶體大小=堆記憶體區 
持久代(永久代、方法區)區大小 程式計數器 Java虛擬機器棧 本地方法棧;
名詞解釋:
1、堆記憶體區:Java程式在執行時建立的所有類例項或陣列都放在同一個堆中。而一個Java虛擬例項中只存在一個堆空間,因此所有執行緒都將共享這個堆。每一個
java程式獨佔一個JVM例項,因而每個 java程式都有它自己的堆空間,它們不會彼此干擾。但是同一java程式的多個執行緒都共享著同一個堆空間,就得考慮多執行緒訪問物件(堆資料)的同步問題。Java
Heap是垃圾收集器管理的主要區域,因此很多時候也被稱為“GC堆”。根據Java虛擬機器規範的規定,Java堆可以處在物理上不連續的記憶體空間中,只要邏輯上是連續的即可。如果在堆中沒有記憶體可分配時,並且堆也無法擴充套件時,將會丟擲OutOfMemoryError:
Java heap space異常。

2、持久代區大小(非堆記憶體):方法區也是各個執行緒共享的記憶體區域,它用於儲存已經被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。方法區域又被稱為“永久代”,但這僅僅對於Sun
HotSpot來講,JRockit和IBM J9虛擬機器中並不存在永久代的概念。Java虛擬機器規範把方法區描述為Java堆的一個邏輯部分,而且它和Java Heap一樣不需要連續的記憶體,可以選擇固定大小或可擴充套件,另外,虛擬機器規範允許該區域可以選擇不實現垃圾回收。相對而言,垃圾收集行為在這個區域比較少出現。該區域的記憶體回收目標主要針是對廢棄常量的和無用類的回收。執行時常量池是方法區的一部分,Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池(Class檔案常量池),用於存放編譯器生成的各種字面量和符號引用,這部分內容將在類載入後存放到方法區的執行時常量池中。執行時常量池相對於Class檔案常量池的另一個重要特徵是具備動態性,Java語言並不要求常量一定只能在編譯期產生,也就是並非預置入Class檔案中的常量池的內容才能進入方法區的執行時常量池,執行期間也可能將新的常量放入池中,這種特性被開發人員利用比較多的是String類的intern()方法。根據Java虛擬機器規範的規定,當方法區無法滿足記憶體分配需求時,將丟擲OutOfMemoryError異常。


3、程式計數器:一塊較小的記憶體空間,它是當前執行緒所執行的位元組碼的行號指示器,位元組碼直譯器工作時通過改變該計數器的值來選擇下一條需要執行的位元組碼指令,分支、跳轉、迴圈等基礎功能都要依賴它來實現。每條執行緒都有一個獨立的的程式計數器,線上程啟動時建立的,各執行緒間的計數器互不影響,因此該區域是執行緒私有的。。當執行緒在執行一個Java方法時,該計數器記錄的是正在執行的虛擬機器位元組碼指令的地址,當執行緒在執行的是Native方法(呼叫本地作業系統方法)時,該計數器的值為空。另外,該記憶體區域是唯一一個在Java虛擬機器規範中沒有規定任何OOM(記憶體溢位:OutOfMemoryError)情況的區域。

4、Java虛擬機器棧:Java
stack 以幀為單位儲存執行緒的執行狀態。虛擬機器只會直接對 Java stack執行兩種操作:以幀為單位的壓棧或出棧。每當執行緒呼叫一個方法的時候,就對當前狀態作為一個幀儲存到 java stack 中(壓棧);當一個方法呼叫返回時,從java stack 彈出一個幀(出棧)。棧的大小是有一定的限制,一般用於存放方法入口引數和返回值,以及原子型別的本地變數(即方法內部變數),這個可能出現StackOverFlow
問題,例如遞迴的層數太深。換一個說法,該區域也是執行緒私有的,它的生命週期也與執行緒相同。虛擬機器棧描述的是Java方法執行的記憶體模型:每個方法被執行的時候都會同時建立一個棧幀,棧它是用於支援續虛擬機器進行方法呼叫和方法執行的資料結構。對於執行引擎來講,活動執行緒中,只有棧頂的棧幀是有效的,稱為當前棧幀,這個棧幀所關聯的方法稱為當前方法,執行引擎所執行的所有位元組碼指令都只針對當前棧幀進行操作。棧幀用於儲存區域性變數表、運算元棧、動態連結、方法返回地址和一些額外的附加資訊。在編譯程式程式碼時,棧幀中需要多大的區域性變數表、多深的運算元棧都已經完全確定了,並且寫入了方法表的Code屬性之中。因此,一個棧幀需要分配多少記憶體,不會受到程式執行期變數資料的影響,而僅僅取決於具體的虛擬機器實現。

在Java虛擬機器規範中,對這個區域規定了兩種異常情況:

1、如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError異常。

2、如果虛擬機器在動態擴充套件棧時無法申請到足夠的記憶體空間,則丟擲OutOfMemoryError異常。

注意點:由於Sun的HotSpot虛擬機器不區分java虛擬機器棧和本地方法棧,因此對於HotSpot虛擬機器來說-Xoss引數(設定本地方法棧大小)雖然存在,但是實際上是無效的,棧容量只能由-Xss引數設定。

5、本地方法棧:該區域與虛擬機器棧所發揮的作用非常相似,只是虛擬機器棧為虛擬機器執行Java方法服務,而本地方法棧則為使用到的本地作業系統(Native)方法服務。當某個執行緒呼叫一個本地方法時,它就進入了一個全新的並且不再受虛擬機器限制的世界。本地方法可以通過本地方法介面來訪問虛擬機器的執行時資料區,不止如此,它還可以做任何它想做的事情。比如,可以呼叫暫存器,或在作業系統中分配記憶體等。總之,本地方法具有和JVM
相同的能力和許可權。  (這裡出現 JVM無法控制的記憶體溢位問題 native heap OutOfMemory ) 。


綜上,從執行緒角度,堆記憶體(Heap)
和持久代區(Method Area) 是被所有執行緒的共享使用的;而Java虛擬機器棧(Java
stack), 程式計數器(Program counter)
和 本地方法棧(Native method stack)
是以執行緒為粒度的,每個執行緒獨自擁有。

被分配的堆記憶體需要進行垃圾回收,先介紹與垃圾回收相關的概念:

1、堆記憶體:在HotSpot虛擬機器中,物理的將堆記憶體分為兩個—年輕代(young generation)和老年代(old generation),即Heap
= { Old NEW = {Eden, from, to} },Old 即 年老代(Old Generation),New 即 年輕代(Young Generation)。年老代和年輕代的劃分對垃圾收集影響比較大。

    a、年輕代:所有新生成的物件首先都是放在年輕代。年輕代的目標就是儘可能快速的收集掉那些生命週期短的物件。年輕代一般分3個區,1個Eden區,2個Survivor區(from 和 to)。大部分物件在Eden區中生成。當Eden區滿時,還存活的物件將被複制到Survivor區(兩個中的一個),當一個Survivor區滿時,此區的存活物件將被複制到另外一個Survivor區,當另一個Survivor區也滿了的時候,從前一個Survivor區複製過來的並且此時還存活的物件,將可能被複制到年老代。2個Survivor區是對稱的,沒有先後關係,所以同一個Survivor區中可能同時存在從Eden區複製過來物件,和從另一個Survivor區複製過來的物件;而複製到年老區的只有從另一個Survivor區過來的物件。而且,因為需要交換的原因,Survivor區至少有一個是空的。特殊的情況下,根據程式需要,Survivor區是可以配置為多個的(多於2個),這樣可以增加物件在年輕代中的存在時間,減少被放到年老代的可能。當物件從這塊記憶體區域消失時,我們說發生了一次“minor
GC”。

 
  b、老年代:在年輕代中經歷了N次(可配置)垃圾回收後仍然存活的物件,存活下來的年輕代物件被複制到這裡。這塊記憶體區域一般大於年輕代。因為它更大的規模,GC發生的次數比在年輕代的少。物件從老年代消失時,我們說“major
GC”(或“full GC”)發生了。


2、持久代:用於存放靜態型別資料,如
Java Class, Method 等。持久代對垃圾回收沒有顯著影響。但是有些應用可能動態生成或呼叫一些Class,例如 Hibernate CGLib 等,在這種時候往往需要設定一個比較大的持久代空間來存放這些執行過程中動態增加的型別。

那麼我們瞭解當一組物件生成時,記憶體申請過程如下:

  1. JVM會試圖為相關Java物件在年輕代的Eden區中初始化一塊記憶體區域。
  2. 當Eden區空間足夠時,記憶體申請結束。否則執行下一步。
  3. JVM試圖釋放在Eden區中所有不活躍的物件(minor GC)。釋放後若Eden空間仍然不足以放入新物件,JVM則試圖將部分Eden區中活躍物件放入Survivor區。
  4. Survivor區被用來作為Eden區及年老代的中間交換區域。當年老代空間足夠時物件會,Survivor區中存活了一定次數的被移到年老代。
  5. 當年老代空間不夠時,JVM會在年老代進行完全的垃圾回收(Full GC)。
  6. Full GC後,若Survivor區及年老代仍然無法存放從Eden區複製過來的物件,則會導致JVM無法在Eden區為新生成的物件申請記憶體,即出現“Out of Memory”。

下面詳細談談每個記憶體設定引數的意義:
A、堆記憶體設定=年輕代大小
年老代大小:
  • -Xms1024M:初始化堆記憶體大小(注意,不加M的話單位是KB),設定JVM初始記憶體為1024m。生產環境下-Xms可以設定與-Xmx相同,以避免每次垃圾回收完成後JVM重新分配記憶體,否則可能需要對Heap
    Size進行頻繁的擴充套件和收縮,增加處理時間;預設值2M,(–server選項把預設值增加到32M)
  • -Xmx2048M:設定JVM最大可用記憶體空間;預設值為64M。(-server選項把預設尺寸增加到128M。)
  • -XX:NewRatio=4:設定年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代),預設值為8。設定為4,則年輕代與年老代所佔比值為1:4,年輕代佔整個堆疊的1/5 
  • -XX:SurvivorRatio=4:設定年輕代中Eden區與Survivor區的大小比值,預設值是10。設定為4,則兩個Survivor區與一個Eden區的比值為2:4,一個Survivor區佔整個年輕代的1/6 

A.1年輕代區記憶體設定(年輕代=Eden區 2Survivor區):
  • -Xmn1024M:直接設定年輕代記憶體值大小為1024M。增大年輕代後,將會減小年老代大小。此值對系統效能影響較大,Sun官方推薦配置為整個堆的3/8;預設值為640K。(-server選項把預設尺寸增加到2M。)注意:-Xmn的優先順序比-XX:NewRatio高,若-Xmn已指定,則OldSize=HeapSize-NewSize,無需再按比例計算。生產環境中一般只需指定-Xmn就足夠了。
  • -XX:NewSize=size in bytes   設定年輕代初始記憶體尺寸。它的預設值是640K。(-server選項把預設尺寸增加到2M。)生產環境中一般只需設定-Xmn或者設定Xmn和NewSize相等,避免調整Size增加處理時間。 
  • -XX:MaxNewSize=size in bytes 設定年輕代的最大記憶體值,新建物件所需的記憶體就是從這個空間中分配來的,這個選項的預設值是640K。(-server選項把預設尺寸增加到2M。)  
      1)調大年輕代的記憶體值能夠減少Full GC次數。   
    2)一般不允許年輕代比年老代還大,因為要考慮GC時最壞情況,所有物件都晉升到老年代。 -XX:MaxNewSize 最大可以設定為-Xmx/2 。
(如上設定年輕代記憶體大小引數的優先順序
    1. 高優先順序:-XX:NewSize/-XX:MaxNewSize 
    2. 中優先順序:-Xmn(預設等效  -Xmn=-XX:NewSize=-XX:MaxNewSize=?) 
    3. 低優先順序:-XX:NewRatio 

A.2垃圾回收記憶體設定(很多垃圾收集器的選項依賴於堆大小的設定。請在微調垃圾收集器使用記憶體空間的方式之前,確認是否已經正確設定了堆的尺寸):
  • -XX:MaxTenuringThreshold=0:設定垃圾最大年齡,每一次經歷GC沒有被回收,則其age自增一,達到設定的閥值後不是被回收就是移至年老代。如果設定為0的話,則年輕代物件不經過Survivor區,直接進入年老代。對於年老代比較多的應用,可以提高效率。如果將此值設定為一個較大值,則年輕代物件會在Survivor區進行多次複製,這樣可以增加物件再年輕代的存活時間,增加在年輕代即被回收的概率。 
  • -XX:MinHeapFreeRatio=percentage as a whole number:修改垃圾回收之後堆中可用記憶體的最小百分比,預設值是40。如果垃圾回收後至少還有40%的堆記憶體沒有被釋放,則系統將增加堆的尺寸。 
  • -XX:MaxHeapFreeRatio=percentage as a whole number: 改變垃圾回收之後和堆記憶體縮小之前可用堆記憶體的最大百分比,預設值為70。這意味著如果在垃圾回收之後還有大於70%的堆記憶體,則系統就會減少堆的尺寸。 
  • -XX:TargetSurvivorRatio=percentage 設定Survivor區的目標使用率,預設值為50.設定較大值可以提高其使用率,一旦存放總大小超過比例,則遷移至年老區。通常可以和-XX:MaxTenuringThreshold配合使用,同時關注設定的老代閥值上限和設定的目標使用率,最優的狀態即大部分的年齡均不大於1,且從未達到設定的使用率,則表明物件均被GC回收。

A.3垃圾回收策略:

JVM給出了3種選擇:序列收集器、並行收集器、併發收集器。序列收集器只適用於小資料量的情況,所以生產環境的選擇主要是並行收集器、併發收集器和G1(“Garbage-First”)收集器。

預設情況下JDK5.0以前都是使用序列收集器,如果想使用其他收集器需要在啟動時加入相應引數。JDK5.0以後,JVM會根據當前系統配置進行智慧判斷。

其中併發與並行存在一定的區別,併發可以理解為在同一個時間間隔內多個任務同時開展,但是通常相互不相關;並行是為了解決同一個任務。

a、序列收集器:適合客戶端使用,不適合伺服器。

  • -XX: UseSerialGC:設定序列收集器。

b、並行收集器(吞吐量優先):parallel收集器使用多執行緒並行處理GC,因此更快。當有足夠大的記憶體和大量芯數時,parallel收集器是有用的。
  • -XX: UseParallelGC:設定為並行收集器。此配置僅對年輕代有效。即年輕代使用並行收集,而年老代仍使用序列收集。
  • -XX:ParallelGCThreads=20:配置並行收集器的執行緒數,即:同時有多少個執行緒一起進行垃圾回收。此值建議配置與CPU數目相等。
  • -XX: UseParallelOldGC:配置年老代垃圾收集方式為並行收集。JDK6.0開始支援對年老代並行收集。
  • -XX:MaxGCPauseMillis=100:設定每次年輕代垃圾回收的最長時間(單位毫秒)。如果無法滿足此時間,JVM會自動調整年輕代大小,以滿足此時間。
  • -XX: UseAdaptiveSizePolicy:設定此選項後,並行收集器會自動調整年輕代Eden區大小和Survivor區大小的比例,以達成目標系統規定的最低響應時間或者收集頻率等指標。此引數建議在使用並行收集器時,一直開啟。

c、併發收集器(響應時間優先):垃圾收集器進行垃圾收集時,其他執行緒的依舊在工作。一旦採取了這種GC型別,由於垃圾回收導致的停頓時間會極其短暫。CMS
收集器也被稱為低延遲垃圾收集器。它經常被用在那些對於響應時間要求十分苛刻的應用上。缺點是它會比其他GC型別佔用更多的記憶體和CPU,預設情況下不支援壓縮步驟,在使用這個GC型別之前需要慎重考慮。如果因為記憶體碎片過多而導致壓縮任務不得不執行,那麼stop-the-world的時間要比其他任何GC型別都長,需要考慮壓縮任務的發生頻率以及執行時間。

  • -XX: UseConcMarkSweepGC:即CMS收集,設定年老代為併發收集。CMS收集是JDK1.4後期版本開始引入的新GC演算法。它的主要適合場景是對響應時間的重要性需求大於對吞吐量的需求,能夠承受垃圾回收執行緒和應用執行緒共享CPU資源,並且應用中存在比較多的長生命週期物件。CMS收集的目標是儘量減少應用的暫停時間,減少Full
    GC發生的機率,利用和應用程式執行緒併發的垃圾回收執行緒來標記清除年老代記憶體。 此選項在Heap Size 比較大而且Full GC收集時間較長的情況下使用更合適。
  • -XX: UseParNewGC:設定年輕代為併發收集。可與CMS收集同時使用。JDK5.0以上,JVM會根據系統配置自行設定,所以無需再設定此引數。
  • -XX:CMSFullGCsBeforeCompaction=0:由於併發收集器不對記憶體空間進行壓縮和整理,所以執行一段時間並行收集以後會產生記憶體碎片,記憶體使用效率降低。此引數設定執行0次Full GC後對記憶體空間進行壓縮和整理,即每次Full GC後立刻開始壓縮和整理記憶體。
  • -XX: UseCMSCompactAtFullCollection:開啟記憶體空間的壓縮和整理,在Full GC後執行。可能會影響效能,但可以消除記憶體碎片。
  • -XX: CMSIncrementalMode:設定為增量收集模式。一般適用於單CPU情況。
  • -XX:CMSInitiatingOccupancyFraction=70:表示年老代記憶體空間使用到70%時就開始執行CMS收集,以確保年老代有足夠的空間接納來自年輕代的物件,避免Full GC的發生。

d、垃圾優先型(G1)收集器:G1是一個適用於伺服器端、大記憶體、多CPU情景的垃圾收集器,主要目標是在維持高效率回收(high
thoughput)的同時,提供軟實時中斷特性。使用者可以指定一個時間上限,如果垃圾回收導致的程式暫停超過了使用者設定的時間上限,會打斷垃圾回收,恢復程式的執行。G1的原理在於將堆劃分成等一系列大小的區域,每一個區域都有一個對應的remembered set結構,用來記錄指向這個區域中的地址的其他區域的指標。 在垃圾回收時,選擇記錄最少的一個區域進行,按找這種方式選擇出來的區域,通常是有用資料最少、垃圾最多的區域,這也就是“Garbage-first
”名稱的由來。假如沒有外部的指標指向這個區域,就可以直接回收整塊區域而不用進行記憶體Copy。如果你想要理解G1收集器,首先你要忘記你所理解的新生代和老年代,在G1中,年輕代和年老代之間沒有物理隔離。每個物件被分配到不同的網格中,隨後執行垃圾回收。當一個區域填滿之後,物件被轉移到另一個區域,並再執行一次垃圾回收。在這種垃圾回收演算法中,不再有從新生代移動到老年代的三部曲。這個型別的垃圾收集演算法是為了替代CMS
收集器而被建立的,因為CMS 收集器在長時間持續執行時會產生很多問題。G1最大的好處是他的效能,他比我們在上面的任何一種GC都要快。

G1有以下屬性:

◆並行和併發性:G1利用了當今硬體中存在的並行性,當Java應用程式的執行緒被停止時,它使用所有可用的CPU(核心,硬體執行緒等)加速其停止,在停止過程中執行Java執行緒最小化整個堆疊。

◆代:和其他HotSpot GC一樣,G1是一代,意味著它在處理新分配的物件(年輕代)和已經生存了一段時間的物件(年老代)時會不同,它主要集中於新物件上的垃圾回收活動,因為它們是最可能回收的,舊物件只是偶爾訪問一下,對於大多數Java應用程式,代的垃圾回收對於替代方案具有重要優勢。

◆壓縮:和CMS不同,G1會隨時間推移對堆疊進行壓縮,壓縮消除了潛在的碎片問題,確保長時間執行的操作流暢和一致。

◆可預測性:G1比CMS預測性更佳,這都是由於消除了碎片問題帶來的好處,再也沒有CMS中停止期間出現的負面影響,另外,G1有一個暫停預測模型,允許它滿足(或很少超過)暫停時間目標。

因為G1GC還不是預設的jvm gc策略(目前為止),需要使用的話可以加入以下引數開啟:

  • -XX: UnlockExperimentalVMOptions
    -XX: UseG1GC   :     #開啟
  • -XX:MaxGCPauseMillis =50                :#最大GC停頓時間,這是個軟目標,JVM將儘可能(但不保證)停頓小於這個時間(暫停時間目標50ms,預設200ms) 
  • -XX:GCPauseIntervalMillis =200         :#使用G1時可以指定時間間隔,當GC暫停持續時間沒有XX:MaxGCPauseMillis給出的時間長則設定(暫停間隔目標200ms) 
  • -XX: G1YoungGenSize=512m           : #年輕代的大小可以明確指定影響消除暫停時間(年輕代大小512M) 
  • -XX:InitiatingHeapOccupancyPercent=n  :#堆佔用了多少比例的時候觸發GC,就即觸發標記週期的
    Java 堆佔用率閾值。預設佔用率是整個 Java 堆的 45%
  • -XX:ParallelGCThreads=n                 :#配置並行收集器的執行緒數,即:同時有多少個執行緒一起進行垃圾回收。將
    n 的值設定為邏輯處理器(CPU數目)的數量。n 的值與邏輯處理器的數量相同,最多為
    8。如果邏輯處理器不止八個,則將 n 的值設定為邏輯處理器數的
    5/8 左右。這適用於大多數情況,除非是較大的 SPARC 系統,其中 n 的值可以是邏輯處理器數的
    5/16 左右。
  • -XX:ConcGCThreads=n                      :#併發GC使用的執行緒數,將 n 設定為並行垃圾回收執行緒數
    (ParallelGCThreads) 的 1/4 左右。
  • -XX:G1ReservePercent=n                   :#設定作為空閒空間的預留記憶體百分比,以降低目標空間溢位的風險。預設值是
    10%,增加或減少百分比時,需確保對總的 Java 堆調整相同的量。Java HotSpot VM build 23 中沒有此設定
  • -XX:G1HeapRegionSize=n                  :#設定的
    G1 區域的大小。值是2的冪,範圍是1 MB 到32 MB。目標是根據最小的 Java 堆大小劃分出約 2048 個區域。
  • -XX:G1NewSizePercent=5
                      :#設定要用作年輕代大小最小值的堆百分比。預設值是 Java 堆的 5%。這是一個實驗性的標誌。此設定取代了 -XX:DefaultMinNewGenPercent 設定。Java
    HotSpot VM build 23 中沒有此設定。    
  • -XX:G1MaxNewSizePercent=60
             :#設定要用作年輕代大小最大值的堆大小百分比。預設值是 Java 堆的 60%。這是一個實驗性的標誌。此設定取代了 -XX:DefaultMaxNewGenPercent 設定。Java
    HotSpot VM build 23 中沒有此設定
  • -XX:G1MixedGCLiveThresholdPercent=65:為混合垃圾回收週期中要包括的舊區域設定佔用率閾值。預設佔用率為
    65%。這是一個實驗性的標誌。此設定取代了 -XX:G1OldCSetRegionLiveThresholdPercent 設定。Java
    HotSpot VM build 23 中沒有此設定。
  • -XX:G1HeapWastePercent=10:設定您願意浪費的堆百分比。如果可回收百分比小於堆廢物百分比,Java HotSpot VM 不會啟動混合垃圾回收週期。預設值是 10%。Java HotSpot VM build 23 中沒有此設定。
  • -XX:G1MixedGCCountTarget=8:設定標記週期完成後,對存活資料上限為 G1MixedGCLIveThresholdPercent 的舊區域執行混合垃圾回收的目標次數。預設值是 8 次混合垃圾回收。混合回收的目標是要控制在此目標次數以內。Java HotSpot VM build 23 中沒有此設定。
  • -XX:G1OldCSetRegionThresholdPercent=10:設定混合垃圾回收期間要回收的最大舊區域數。預設值是
    Java 堆的 10%。Java HotSpot VM build 23 中沒有此設定。

    #據稱下面兩個引數可能會引發一個罕見的競爭狀態(race
condition),慎用。
    -XX: G1ParallelRSetUpdatingEnabled

    -XX: G1ParallelRSetScanningEnabled

    G1實際應用建議:
  • 年輕代大小:避免使用 -Xmn 選項或 -XX:NewRatio 等其他相關選項顯式設定年輕代大小,因為固定年輕代的大小會覆蓋暫停時間目標。
  • 暫停時間目標:每當對垃圾回收進行評估或調優時,都會涉及到延遲與吞吐量的權衡。G1是增量垃圾回收器, 其吞吐量目標是 90% 的應用程式時間和 10%的垃圾回收時間。因此,暫停時間目標不要太嚴苛。目標太過嚴苛表示您願意承受更多的垃圾回收開銷,而這會直接影響到吞吐量。
  • 掌握混合垃圾回收:當您調優混合垃圾回收時,請嘗試以下選項
    • -XX:InitiatingHeapOccupancyPercent
    • -XX:G1MixedGCLiveThresholdPercent 和 -XX:G1HeapWastePercent
    • -XX:G1MixedGCCountTarget 和 -XX:G1OldCSetRegionThresholdPercent

其它垃圾回收引數

  • -XX: ScavengeBeforeFullGC:年輕代GC優於Full GC執行。
  • -XX: DisableExplicitGC:不響應 System.gc() 程式碼。
  • -XX: UseThreadPriorities:啟用本地執行緒優先順序API。即使 java.lang.Thread.setPriority() 生效,不啟用則無效。
  • -XX:SoftRefLRUPolicyMSPerMB=0:軟引用物件在最後一次被訪問後能存活0毫秒(JVM預設為1000毫秒)。

輔助資訊引數設定

  • -XX: CITime:列印消耗在JIT編譯的時間。
  • -XX:ErrorFile=./hs_err_pid.log:儲存錯誤日誌或資料到指定檔案中。
  • -XX:HeapDumpPath=./java_pid.hprof:指定Dump堆記憶體時的路徑,拿到heap檔案後可以用Eclipse MAT進行分析,找出引起記憶體洩漏的class。
  • -XX: HeapDumpOnOutOfMemoryError:當首次遭遇記憶體溢位時Dump出此時的堆記憶體,與-XX:HeapDumpPath一起使用。
  • -XX:OnError=”;”:出現致命ERROR後執行自定義命令。
  • -XX:OnOutOfMemoryError=”;”:當首次遭遇記憶體溢位時執行自定義命令。
  • -XX: PrintClassHistogram:按下 Ctrl Break 後列印堆記憶體中類例項的柱狀資訊,同JDK的 jmap -histo 命令。
  • -XX: PrintConcurrentLocks:按下 Ctrl Break 後列印執行緒棧中併發鎖的相關資訊,同JDK的 jstack -l 命令。
  • -XX: PrintCompilation:當一個方法被編譯時列印相關資訊。
  • -XX: PrintGC:每次GC時列印相關資訊,與 -verbose:gc 是一樣的,可以認為-verbose:gc 是 -XX: PrintGC的別名,列印日誌稍有差異。
  • -XX: PrintGCDetails:每次GC時列印詳細資訊。
  • -XX: PrintGCTimeStamps:列印每次GC的時間戳。
  • -XX: TraceClassLoading:跟蹤類的載入資訊。
  • -XX: TraceClassLoadingPreorder:跟蹤被引用到的所有類的載入資訊。
  • -XX: TraceClassResolution:跟蹤常量池。
  • -XX: TraceClassUnloading:跟蹤類的解除安裝資訊。
  • -Xloggc:../logs/gc.log 垃圾收集日誌檔案的輸出路徑

關於引數名稱等

  • 標準引數(-),所有JVM都必須支援這些引數的功能,而且向後相容;例如:
    • -client——設定JVM使用Client模式,特點是啟動速度比較快,但執行時效能和記憶體管理效率不高,通常用於客戶端應用程式或開發除錯;在32位環境下直接執行Java程式預設啟用該模式。
    • -server——設定JVM使Server模式,特點是啟動速度比較慢,但執行時效能和記憶體管理效率很高,適用於生產環境。在具有64位能力的JDK環境下預設啟用該模式。
  • 非標準引數(-X),預設JVM實現這些引數的功能,但是並不保證所有JVM實現都滿足,且不保證向後相容;
  • 非穩定引數(-XX),此類引數各個JVM實現會有所不同,將來可能會不被支援,需要慎重使用;

B、持久代區記憶體設定:
  • -XX:PermSize=512M:初始化持久區(啟動是主要是類載入)記憶體池大小;生產環境中一般設定MaxPermSize和PermSize相等,避免調整size需要的處理時間。
  • -XX:MaxPermSize=2048M:最大類持久區記憶體池大小;預設值32M。

C、Java虛擬機器棧記憶體設定:
  • -Xss128k:設定每個執行緒的堆疊大小。JDK5.0以後每個執行緒堆疊大小為1M,以前每個執行緒堆疊大小為256K。根據應用的執行緒所需記憶體大小進行調整。在相同實體記憶體下,減小這個值能生成更多的執行緒。但是作業系統對一個程序內的執行緒數還是有限制的,不能無限生成,經驗值在3000~5000左右;


Z、直接記憶體設定(Direct
Memory Size):

  • -XX:MaxDirectMemorySize —direct byte buffer用到的本地記憶體。預設跟Xmx相等,所以生產環境中一般不設定Xmx大於實體記憶體的一半

直接記憶體並不是虛擬機器執行時資料區的一部分,也不是Java虛擬機器規範中定義的記憶體區域,它直接從作業系統中分配,因此不受Java堆大小的限制,但是會受到本機總記憶體的大小及處理器定址空間的限制,因此它也可能導致OutOfMemoryError異常出現。在JDK1.4中新引入了NIO機制,它是一種基於通道與緩衝區的新I/O方式,可以直接從作業系統中分配直接記憶體,即在堆外分配記憶體,這樣能在一些場景中提高效能,因為避免了在Java堆和Native堆中來回複製資料。


大型網站伺服器案例

承受海量訪問的動態Web應用

伺服器配置:8 CPU, 8G MEM, JDK 1.6.X

引數方案:

-server -Xmx3550m -Xms3550m -Xmn1256m -Xss128k -XX:SurvivorRatio=6 -XX:MaxPermSize=256m -XX:ParallelGCThreads=8 -XX:MaxTenuringThreshold=0 -XX: UseConcMarkSweepGC

調優說明:

  • -Xmx 與 -Xms 相同以避免JVM反覆重新申請記憶體。-Xmx 的大小約等於系統記憶體大小的一半,即充分利用系統資源,又給予系統安全執行的空間。
  • -Xmn1256m 設定年輕代大小為1256MB。此值對系統效能影響較大,Sun官方推薦配置年輕代大小為整個堆的3/8。
  • -Xss128k 設定較小的執行緒棧以支援建立更多的執行緒,支援海量訪問,並提升系統效能。
  • -XX:SurvivorRatio=6 設定年輕代中Eden區與Survivor區的比值。系統預設是8,根據經驗設定為6,則2個Survivor區與1個Eden區的比值為2:6,一個Survivor區佔整個年輕代的1/8。
  • -XX:ParallelGCThreads=8 配置並行收集器的執行緒數,即同時8個執行緒一起進行垃圾回收。此值一般配置為與CPU數目相等。
  • -XX:MaxTenuringThreshold=0 設定垃圾最大年齡(在年輕代的存活次數)。如果設定為0的話,則年輕代物件不經過Survivor區直接進入年老代。對於年老代比較多的應用,可以提高效率;如果將此值設定為一個較大值,則年輕代物件會在Survivor區進行多次複製,這樣可以增加物件再年輕代的存活時間,增加在年輕代即被回收的概率。根據被海量訪問的動態Web應用之特點,其記憶體要麼被快取起來以減少直接訪問DB,要麼被快速回收以支援高併發海量請求,因此其記憶體物件在年輕代存活多次意義不大,可以直接進入年老代,根據實際應用效果,在這裡設定此值為0。
  • -XX: UseConcMarkSweepGC 設定年老代為併發收集。CMS(ConcMarkSweepGC)收集的目標是儘量減少應用的暫停時間,減少Full GC發生的機率,利用和應用程式執行緒併發的垃圾回收執行緒來標記清除年老代記憶體,適用於應用中存在比較多的長生命週期物件的情況。


最後從記憶體溢位的異常來分析下我們設定的每個引數具體對應關係:
1、java.lang.OutOfMemoryError:
Java heap space —-JVM
Heap(堆)溢位

JVM堆的設定是指java程式執行過程中JVM可以調配使用的記憶體空間的設定.JVM在啟動的時候會自動設定Heap size的值,其初始空間(即-Xms)是實體記憶體的1/64,最大空間(-Xmx)是實體記憶體的1/4。可以利用JVM提供的-Xmn
-Xms -Xmx等選項可進行設定。Heap size 的大小是Young Generation 和Old Generaion 之和。
提示:在JVM中如果98%的時間是用於GC且可用的Heap size 不足2%的時候將丟擲此異常資訊。

建議:Heap Size 最大不要超過可用實體記憶體的80%,一般的要將-Xms和-Xmx選項設定為相同,而-Xmn為1/4的-Xmx值(與sun推薦的3/8稍有差異)。 
解決方法:調整Heap size大小。

2、 java.lang.OutOfMemoryError:
PermGen space  —- PermGen space溢位

PermGen space的全稱是Permanent Generation space,是指記憶體的永久儲存區域。為什麼會記憶體溢位,這是由於這塊記憶體主要是被JVM存放Class和Meta資訊的,Class在被Loader的時候被放入PermGen
space區域,它和存放類例項(Instance)的Heap區域不同。sun的GC不會在主程式執行期對PermGen space進行清理,所以如果你的應用中有很多CLASS的話,就很可能出現PermGen space溢位。

建議:這種錯誤常見在web伺服器對JSP進行pre
compile的時候。如果你的WEB 應用下都用了大量的第三方jar, 其大小超過了jvm預設的大小(4M)那麼就會產生此錯誤資訊了。將相同的第三方jar檔案移置到tomcat/shared/lib目錄下,這樣可以達到減少jar
文件重複佔用記憶體的目的。

解決方法: 調整XX:MaxPermSize大小。


3、java.lang.StackOverflowError   —-
棧溢位

JVM依然是採用棧式的虛擬機器,這個和C和Pascal都是一樣的。函式的呼叫過程都體現在堆疊和退棧上了。呼叫建構函式的 “層”太多了,以致於把棧區溢位了。通常來講,一般棧區遠遠小於堆區的,因為函式呼叫過程往往不會多於上千層,而即便每個函式呼叫需要 1K的空間(這個大約相當於在一個C函式內宣告瞭256個int型別的變數),那麼棧區也不過是需要1MB的空間。通常棧的大小是1-2MB的。通常遞迴即使遞迴的層次不會過多,也很容易溢位。

解決方法:修改程式。

記憶體溢位

下面給出個記憶體區域記憶體溢位的簡單測試方法

這裡有一點要重點說明,在多執行緒情況下,給每個執行緒的棧分配的記憶體越大,反而越容易產生記憶體溢位異常。作業系統為每個程序分配的記憶體是有限制的,虛擬機器提供了引數來控制Java堆和方法區這兩部分記憶體的最大值,忽略掉程式計數器消耗的記憶體(很小),以及程序本身消耗的記憶體,剩下的記憶體便給了虛擬機器棧和本地方法棧,每個執行緒分配到的棧容量越大,可以建立的執行緒數量自然就越少。因此,如果是建立過多的執行緒導致的記憶體溢位,在不能減少執行緒數的情況下,就只能通過減少最大堆和每個執行緒的棧容量來換取更多的執行緒。

另外,由於Java堆內也可能發生記憶體洩露(Memory Leak),這裡簡要說明一下記憶體洩露和記憶體溢位的區別:

記憶體洩露是指分配出去的記憶體沒有被回收回來,由於失去了對該記憶體區域的控制,因而造成了資源的浪費。Java中一般不會產生記憶體洩露,因為有垃圾回收器自動回收垃圾,但這也不絕對,當我們new了物件,並儲存了其引用,但是後面一直沒用它,而垃圾回收器又不會去回收它,這邊會造成記憶體洩露,

記憶體溢位是指程式所需要的記憶體超出了系統所能分配的記憶體(包括動態擴充套件)的上限。


物件例項化分析

對記憶體分配情況分析最常見的示例便是物件例項化:

Object obj = new Object();

這段程式碼的執行會涉及java棧、Java堆、方法區三個最重要的記憶體區域。假設該語句出現在方法體中,及時對JVM虛擬機器不瞭解的Java使用這,應該也知道obj會作為引用型別(reference)的資料儲存在Java棧的本地變數表中,而會在Java堆中儲存該引用的例項化物件,但可能並不知道,Java堆中還必須包含能查詢到此物件型別資料的地址資訊(如物件型別、父類、實現的介面、方法等),這些型別資料則儲存在方法區中。

另外,由於reference型別在Java虛擬機器規範裡面只規定了一個指向物件的引用,並沒有定義這個引用應該通過哪種方式去定位,以及訪問到Java堆中的物件的具體位置,因此不同虛擬機器實現的物件訪問方式會有所不同,主流的訪問方式有兩種:使用控制代碼池和直接使用指標。

通過控制代碼池訪問的方式如下:

通過直接指標訪問的方式如下:

 這兩種物件的訪問方式各有優勢,使用控制代碼訪問方式的最大好處就是reference中存放的是穩定的控制代碼地址,在物件被移動(垃圾收集時移動物件是非常普遍的行為)時只會改變控制代碼中的例項資料指標,而reference本身不需要修改。使用直接指標訪問方式的最大好處是速度快,它節省了一次指標定位的時間開銷。目前Java預設使用的HotSpot虛擬機器採用的便是是第二種方式進行物件訪問的。


參考資料:

Java中的垃圾回收機制:http://mp.weixin.qq.com/s?__biz=MjM5NzMyMjAwMA==&mid=2651477309&idx=1&sn=ab7ffd42cb2632721c5810529a3d1806&scene=23&srcid=0714aSeVNPU59W346VKtp5Ky#rd

Java7中G1垃圾回收收集器使用:http://ju.outofmemory.cn/entry/65368
Java7中G1垃圾回收收集器特性:http://developer.51cto.com/art/200907/138943.htm
垃圾優先型垃圾回收器調優:http://www.oracle.com/technetwork/cn/articles/java/g1gc-1984535-zhs.html