1. 程式人生 > >JVM相關引數相關整理

JVM相關引數相關整理

堆和棧空間設定:

 

堆空間:heap=young+old,既:堆空間=年輕代+老年代。其中年輕代:young=2*survivor+eden,既:年輕代=兩個survivor空間+1個eden空間。堆空間儲存了new出物件和陣列的實際資料,也是gc最愛回收的。同時對對空間進行了分代管理:

 

年輕代:建立的新物件會被放入年輕代的eden空間,而年輕代gc採用複製演算法,複製演算法會把記憶體分為兩個區域(即兩個survivor空間:from和to)。當進行一次minor gc時(既年輕代的gc),minor gc是序列的,eden空間如果沒有被gc root引用的會被回收,而依然存活的會被移動到from空間中,如果from空間在minor gc時物件依舊可以存活,就會對該物件年齡+1,當年齡達到一定數值時會直接放入老年代,沒有達到年齡的存活物件會被複制到to中。這時from和eden空間已經被清空,虛擬機器會交換from和to的空間,空的from變成to,to的變成from,保證了to是空的,minor gc會不斷重複這樣的工作,直到to徹底被填滿,這時會將物件移動到老年代。

 

老年代:老年代空間的物件是經過minor gc反覆錘鍊出來的。老年代使用並行的gc回收期,標記-清除演算法,並且產生的是full gc(major gc)。老年代gc雖然是並行的,但full gc會同時對年輕代進行gc,所以大量的full gc會嚴重耗費jvm的效能,甚至卡死應用。另外可以大物件會直接分配到老年代,避免了在minor gc對兩個survivor空間的複製耗時。

 

永久代:也就是方法棧注意java8中永久代被徹底移除了。永久代包含了Class的元資訊和常量池。該區域是gc最不愛回收的。

 

棧空間:分為本地方法棧和虛擬機器棧,本地方法棧是被宣告為native的方法存放的空間。虛擬機器棧是我們通常說的棧,執行緒私有,隨著執行緒銷燬而銷燬,gc是不管這裡的。包含了:區域性變量表、運算元棧、動態連結串列、方法出口資訊等。我們常用的hotspot把本地方法棧和虛擬機器棧合成了一個。

 

-Xmx:最大堆記憶體,如 java -Xmx1024m

-Xms:初始化堆記憶體大小,如 java -Xmx1024m -Xms1024m,注意如果這最大和初始化堆大小設定相同的話,可以防止jvm的堆記憶體動態擴容

-Xmn:年輕代空間大小,如java -Xmx1024m -Xms1024m -Xmn256m

-Xss:執行緒棧大小,如java -Xmx1024m -Xms1024m -Xmn256m -Xss128k,注意jdk1.5之前每個執行緒棧預設為256k,之後是1m,越多的執行緒棧空間能換取的執行緒數越少,反之越少的執行緒棧空間能換取的執行緒數越多

-Xoss:本地方法棧大小,對hotspot無效

-XX:NewRatio:設定年輕代與老年代的比例,如java -Xmx1024m -Xms1024m -Xss128k -XX:NewRatio=4

-XX:SurvivorRatio:設定survivor空間佔年輕代空間的比例,如:java -Xmx1024m -Xms1024m -Xmn256m -XX:SurvivorRatio=4,eden:survivor=4:2

-XX:MaxPermSize:永久代空間大小,如:java -Xmx1024m -Xms1024m -Xss128k -XX:MaxPermSize=16m

-XX:MaxTenuringThreshold:年輕代最大gc年齡,如果超過這個閾值會直接接入老年代,如果設定為0,年輕代不經過survivor空間直接進入老年代,如:java -Xmx1024m -Xms1024m -Xss128k -XX:MaxTenuringThreshold=0

-XX:PretenureSizeThreshold:設定大物件直接進入老年代的閾值,當大物件大小超過該值將會直接在老年代分配。如:java -Xmx1024m -Xms1024m -XX:PretenureSizeThreshold=5242880

垃圾收集器設定: 

在jdk1.6中提供的gc年輕代分為:Serial、Parallel Scavenge、ParNew,而老年代分為:Serial、Parallel、CMS。在jdk1.7中加入了G1。其中Serial為序列gc,是jvm引數指定-client時使用的預設gc。Parallel和ParNew為序列gc,jvm引數指定-server時使用的預設gc為Parallel Scavenge,同樣CMS為老年代並行gc,G1為jdk1.7實驗性gc,為java9預設的gc。 

序列gc在垃圾回收時會stop the world,既停止當前使用者執行緒,然後進行垃圾回收,這樣做的目的是防止使用者執行緒繼續執行產生記憶體碎片。而序列gc在垃圾回收時通常不會stop the world,而CMS gc會進行多次垃圾回收(期間會進行一次短暫的stop the world)或者壓縮來減少記憶體碎片。 

另外Parallel Scavenge和ParNew的區別在於Parallel Scavenge更關注與吞吐量,既吞吐量配置引數可控。 

G1垃圾回收器將記憶體從分代轉換為分塊,將記憶體分配為多塊大小相等的heap,每個heap有獨立的eden、survivor、old空間,在記憶體邏輯上都是連續的。採用並行標記壓縮方式。

 

-XX:+UseSerialGC:在新生代和老年代使用串休gc

-XX:+UseParNewGc:在新生代使用並行gc

-XX:+UseParallelOldGC:在老年代使用並行gc

-XX:ParallelGCThread:設定Parallel gc的垃圾回收執行緒數,通常與cpu數量相同

-XX:MaxGCPauseMillis:設定最大垃圾收集停頓時間,垃圾回收器會盡量控制回收的時間在該值範圍內

-XX:GCPauseIntervalMillis:設定停頓時間間隔

-XX:GCTimeRatio:設定吞吐量大小,0~100之間的整數。若該值為n,那麼jvm將會花費不超過1/(1+n)的時間用於垃圾回收。

-XX:+UseAdaptiveSizePolicy:開啟自適應gc策略,jvm會根據執行時吞吐量等資訊自動調整eden、old等空間大小以及晉升老年代年齡

-XX:+UseConcMarkSweepGC:新生代使用ParNew,老年代使用CMS和Serial,其中老年代的Serial用於作為CMS失敗時呼叫的備選gc

-XX:+ParallelCMSThreads:CMS執行緒數量

-XX:CMSInitiatingOccupancyFraction:設定老年代空間被使用多少後觸發CMS gc,預設為68%

-XX:CMSFullGCsBeforeCompaction:設定多少次CMS回收後,進行一次記憶體壓縮

-XX:+CMSClassUnloadingEnabled:在類解除安裝後進行CMS回收

-XX:+CMSParallelRemarkEnabled:啟用並行重標記

-XX:CMSInitiatingPermOccupancyFraction:當永久代空間被使用多少後觸發CMS gc,百分比(在使用時CMSClassUnloadingEnabled必須被配置)

UseCMSInitiatingOccupancyOnly:只有當gc達到配置的閾值時才進行回收

XX:+CMSIncrementalMode:使用增量模式,適合單CPU

XX:+UserG1GC:使用G1回收器,與G1相關的虛擬機器引數都只能在jdk1.7以上使用

XX:+UnlockExperimentalVMOptions:允許使用實驗性引數 

輔助設定:

-XX:+PrintGC:輸出GC垃圾回收日誌

-verbose:gc:與-XX:+PrintGC相同

-XX:+PrintGCDetail:輸出詳細的GC垃圾回收日誌

-XX:+PrintGCTimeStamps:輸出GC回收的時間戳

-XX:+PrintGCApplicationStoppedTIme:輸出GC垃圾回收時所佔用的停頓時間

-XX:+PrintGCApplicationConcurrentTime:輸出GC並行回收時所佔用的時間

-XX:+PrintHeapAtGC:輸出GC前後詳細的堆資訊

-Xloggc:filename:把GC日誌輸出到filename指定的檔案

-XX:+PrintClassHistogram:輸出類資訊

-XX:+PrintTLAB:輸出TLAB空間使用情況

-XX:+PrintTenuringDistribution:輸出每次minor GC後新的存活物件的年齡閾值

--------------------- 

 

 

 

GC Roots

我們先來了解一下在Java中是如何判斷一個物件的生死的,有些語言比如Python是採用引用計數來統計的,但是這種做法可能會遇見迴圈引用的問題,在Java以及C#等語言中是採用GC Roots來解決這個問題。如果一個物件和GC Roots之間沒有連結,那麼這個物件也可以被視作是一個可回收的物件。

Java中可以被作為GC Roots中的物件有:

虛擬機器棧中的引用的物件。
方法區中的類靜態屬性引用的物件。
方法區中的常量引用的物件。
本地方法棧(jni)即一般說的Native的引用物件。

垃圾回收演算法

標記清除

標記-清除演算法將垃圾回收分為兩個階段:標記階段和清除階段。在標記階段首先通過根節點,標記所有從根節點開始的物件,未被標記的物件就是未被引用的垃圾物件。然後,在清除階段,清除所有未被標記的物件。標記清除演算法帶來的一個問題是會存在大量的空間碎片,因為回收後的空間是不連續的,這樣給大物件分配記憶體的時候可能會提前觸發full gc。

複製演算法

將現有的記憶體空間分為兩快,每次只使用其中一塊,在垃圾回收時將正在使用的記憶體中的存活物件複製到未被使用的記憶體塊中,之後,清除正在使用的記憶體塊中的所有物件,交換兩個記憶體的角色,完成垃圾回收。

現在的商業虛擬機器都採用這種收集演算法來回收新生代,IBM研究表明新生代中的物件98%是朝夕生死的,所以並不需要按照1:1的比例劃分記憶體空間,而是將記憶體分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中的一塊Survivor。當回收時,將Eden和Survivor中還存活著的物件一次性地拷貝到另外一個Survivor空間上,最後清理掉Eden和剛才用過的Survivor的空間。HotSpot虛擬機器預設Eden和Survivor的大小比例是8:1(可以通過-SurvivorRattio來配置),也就是每次新生代中可用記憶體空間為整個新生代容量的90%,只有10%的記憶體會被“浪費”。當然,98%的物件可回收只是一般場景下的資料,我們沒有辦法保證回收都只有不多於10%的物件存活,當Survivor空間不夠用時,需要依賴其他記憶體(這裡指老年代)進行分配擔保。

標記整理

複製演算法的高效性是建立在存活物件少、垃圾物件多的前提下的。這種情況在新生代經常發生,但是在老年代更常見的情況是大部分物件都是存活物件。如果依然使用複製演算法,由於存活的物件較多,複製的成本也將很高。
標記-壓縮演算法是一種老年代的回收演算法,它在標記-清除演算法的基礎上做了一些優化。首先也需要從根節點開始對所有可達物件做一次標記,但之後,它並不簡單地清理未標記的物件,而是將所有的存活物件壓縮到記憶體的一端。之後,清理邊界外所有的空間。這種方法既避免了碎片的產生,又不需要兩塊相同的記憶體空間,因此,其價效比比較高。

增量演算法

增量演算法的基本思想是,如果一次性將所有的垃圾進行處理,需要造成系統長時間的停頓,那麼就可以讓垃圾收集執行緒和應用程式執行緒交替執行。每次,垃圾收集執行緒只收集一小片區域的記憶體空間,接著切換到應用程式執行緒。依次反覆,直到垃圾收集完成。使用這種方式,由於在垃圾回收過程中,間斷性地還執行了應用程式程式碼,所以能減少系統的停頓時間。但是,因為執行緒切換和上下文轉換的消耗,會使得垃圾回收的總體成本上升,造成系統吞吐量的下降。

垃圾回收器

Serial收集器

Serial收集器是最古老的收集器,它的缺點是當Serial收集器想進行垃圾回收的時候,必須暫停使用者的所有程序,即stop the world。到現在為止,它依然是虛擬機器執行在client模式下的預設新生代收集器,與其他收集器相比,對於限定在單個CPU的執行環境來說,Serial收集器由於沒有執行緒互動的開銷,專心做垃圾回收自然可以獲得最高的單執行緒收集效率。

Serial Old是Serial收集器的老年代版本,它同樣是一個單執行緒收集器,使用”標記-整理“演算法。這個收集器的主要意義也是被Client模式下的虛擬機器使用。在Server模式下,它主要還有兩大用途:一個是在JDK1.5及以前的版本中與Parallel Scanvenge收集器搭配使用,另外一個就是作為CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure的時候使用。

通過指定-UseSerialGC引數,使用Serial + Serial Old的序列收集器組合進行記憶體回收。

ParNew收集器

ParNew收集器是Serial收集器新生代的多執行緒實現,注意在進行垃圾回收的時候依然會stop the world,只是相比較Serial收集器而言它會執行多條程序進行垃圾回收。

ParNew收集器在單CPU的環境中絕對不會有比Serial收集器更好的效果,甚至由於存線上程互動的開銷,該收集器在通過超執行緒技術實現的兩個CPU的環境中都不能百分之百的保證能超越Serial收集器。當然,隨著可以使用的CPU的數量增加,它對於GC時系統資源的利用還是很有好處的。它預設開啟的收集執行緒數與CPU的數量相同,在CPU非常多(譬如32個,現在CPU動輒4核加超執行緒,伺服器超過32個邏輯CPU的情況越來越多了)的環境下,可以使用-XX:ParallelGCThreads引數來限制垃圾收集的執行緒數。

-UseParNewGC: 開啟此開關後,使用ParNew + Serial Old的收集器組合進行記憶體回收,這樣新生代使用並行收集器,老年代使用序列收集器。

Parallel Scavenge收集器

Parallel是採用複製演算法的多執行緒新生代垃圾回收器,似乎和ParNew收集器有很多的相似的地方。但是Parallel Scanvenge收集器的一個特點是它所關注的目標是吞吐量(Throughput)。所謂吞吐量就是CPU用於執行使用者程式碼的時間與CPU總消耗時間的比值,即吞吐量=執行使用者程式碼時間 / (執行使用者程式碼時間 + 垃圾收集時間)。停頓時間越短就越適合需要與使用者互動的程式,良好的響應速度能夠提升使用者的體驗;而高吞吐量則可以最高效率地利用CPU時間,儘快地完成程式的運算任務,主要適合在後臺運算而不需要太多互動的任務。

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,採用多執行緒和”標記-整理”演算法。這個收集器是在jdk1.6中才開始提供的,在此之前,新生代的Parallel Scavenge收集器一直處於比較尷尬的狀態。原因是如果新生代Parallel Scavenge收集器,那麼老年代除了Serial Old(PS MarkSweep)收集器外別無選擇。由於單執行緒的老年代Serial Old收集器在服務端應用效能上的”拖累“,即使使用了Parallel Scavenge收集器也未必能在整體應用上獲得吞吐量最大化的效果,又因為老年代收集中無法充分利用伺服器多CPU的處理能力,在老年代很大而且硬體比較高階的環境中,這種組合的吞吐量甚至還不一定有ParNew加CMS的組合”給力“。直到Parallel Old收集器出現後,”吞吐量優先“收集器終於有了比較名副其實的應用祝賀,在注重吞吐量及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge加Parallel Old收集器。

-UseParallelGC: 虛擬機器執行在Server模式下的預設值,開啟此開關後,使用Parallel Scavenge + Serial Old的收集器組合進行記憶體回收。-UseParallelOldGC: 開啟此開關後,使用Parallel Scavenge + Parallel Old的收集器組合進行垃圾回收

CMS收集器

CMS(Concurrent Mark Swep)收集器是一個比較重要的回收器,現在應用非常廣泛,我們重點來看一下,CMS一種獲取最短回收停頓時間為目標的收集器,這使得它很適合用於和使用者互動的業務。從名字(Mark Swep)就可以看出,CMS收集器是基於標記清除演算法實現的。它的收集過程分為四個步驟:

  • 初始標記(initial mark)

  • 併發標記(concurrent mark)

  • 重新標記(remark)

  • 併發清除(concurrent sweep)

注意初始標記和重新標記還是會stop the world,但是在耗費時間更長的併發標記和併發清除兩個階段都可以和使用者程序同時工作。

不過由於CMS收集器是基於標記清除演算法實現的,會導致有大量的空間碎片產生,在為大物件分配記憶體的時候,往往會出現老年代還有很大的空間剩餘,但是無法找到足夠大的連續空間來分配當前物件,不得不提前開啟一次Full GC。為了解決這個問題,CMS收集器預設提供了一個-XX:+UseCMSCompactAtFullCollection收集開關引數(預設就是開啟的),用於在CMS收集器進行FullGC完開啟記憶體碎片的合併整理過程,記憶體整理的過程是無法併發的,這樣記憶體碎片問題倒是沒有了,不過停頓時間不得不變長。虛擬機器設計者還提供了另外一個引數-XX:CMSFullGCsBeforeCompaction引數用於設定執行多少次不壓縮的FULL GC後跟著來一次帶壓縮的(預設值為0,表示每次進入Full GC時都進行碎片整理)。

不幸的是,它作為老年代的收集器,卻無法與jdk1.4中已經存在的新生代收集器Parallel Scavenge配合工作,所以在jdk1.5中使用cms來收集老年代的時候,新生代只能選擇ParNew或Serial收集器中的一個。ParNew收集器是使用-XX:+UseConcMarkSweepGC選項啟用CMS收集器之後的預設新生代收集器,也可以使用-XX:+UseParNewGC選項來強制指定它。

由於CMS收集器現在比較常用,下面我們再額外瞭解一下CMS演算法的幾個常用引數:

UseCMSInitatingOccupancyOnly:表示只在到達閾值的時候,才進行 CMS 回收。
為了減少第二次暫停的時間,通過-XX:+CMSParallelRemarkEnabled開啟並行remark。如果ramark時間還是過長的話,可以開啟-XX:+CMSScavengeBeforeRemark選項,強制remark之前開啟一次minor gc,減少remark的暫停時間,但是在remark之後也立即開始一次minor gc。
CMS預設啟動的回收執行緒數目是(ParallelGCThreads + 3)/4,如果你需要明確設定,可以通過-XX:+ParallelCMSThreads來設定,其中-XX:+ParallelGCThreads代表的年輕代的併發收集執行緒數目。
CMSClassUnloadingEnabled: 允許對類元資料進行回收。
CMSInitatingPermOccupancyFraction:當永久區佔用率達到這一百分比後,啟動 CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)。
CMSIncrementalMode:使用增量模式,比較適合單 CPU。
UseCMSCompactAtFullCollection引數可以使 CMS 在垃圾收集完成後,進行一次記憶體碎片整理。記憶體碎片的整理並不是併發進行的。
UseFullGCsBeforeCompaction:設定進行多少次 CMS 垃圾回收後,進行一次記憶體壓縮。
一些建議
對於Native Memory:

使用了NIO或者NIO框架(Mina/Netty)

使用了DirectByteBuffer分配位元組緩衝區

使用了MappedByteBuffer做記憶體對映

由於Native Memory只能通過FullGC回收,所以除非你非常清楚這時真的有必要,否則不要輕易呼叫System.gc()。

另外為了防止某些框架中的System.gc呼叫(例如NIO框架、Java RMI),建議在啟動引數中加上-XX:+DisableExplicitGC來禁用顯式GC。這個引數有個巨大的坑,如果你禁用了System.gc(),那麼上面的3種場景下的記憶體就無法回收,可能造成OOM,如果你使用了CMS GC,那麼可以用這個引數替代:-XX:+ExplicitGCInvokesConcurrent。

此外除了CMS的GC,其實其他針對old gen的回收器都會在對old gen回收的同時回收young gen。

G1收集器

G1收集器是一款面向服務端應用的垃圾收集器。HotSpot團隊賦予它的使命是在未來替換掉JDK1.5中釋出的CMS收集器。與其他GC收集器相比,G1具備如下特點:

並行與併發:G1能更充分的利用CPU,多核環境下的硬體優勢來縮短stop the world的停頓時間。

分代收集:和其他收集器一樣,分代的概念在G1中依然存在,不過G1不需要其他的垃圾回收器的配合就可以獨自管理整個GC堆。

空間整合:G1收集器有利於程式長時間執行,分配大物件時不會無法得到連續的空間而提前觸發一次GC。

可預測的非停頓:這是G1相對於CMS的另一大優勢,降低停頓時間是G1和CMS共同的關注點,能讓使用者明確指定在一個長度為M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒。

在使用G1收集器時,Java堆的記憶體佈局和其他收集器有很大的差別,它將這個Java堆分為多個大小相等的獨立區域,雖然還保留新生代和老年代的概念,但是新生代和老年代不再是物理隔離的了,它們都是一部分Region(不需要連續)的集合。

雖然G1看起來有很多優點,實際上CMS還是主流。

與GC相關的常用引數

除了上面提及的一些引數,下面補充一些和GC相關的常用引數:

-Xmx: 設定堆記憶體的最大值。

-Xms: 設定堆記憶體的初始值。

-Xmn: 設定新生代的大小。

-Xss: 設定棧的大小。

-PretenureSizeThreshold: 直接晉升到老年代的物件大小,設定這個引數後,大於這個引數的物件將直接在老年代分配。

-MaxTenuringThrehold: 晉升到老年代的物件年齡。每個物件在堅持過一次Minor GC之後,年齡就會加1,當超過這個引數值時就進入老年代。

-UseAdaptiveSizePolicy: 在這種模式下,新生代的大小、eden 和 survivor 的比例、晉升老年代的物件年齡等引數會被自動調整,以達到在堆大小、吞吐量和停頓時間之間的平衡點。在手工調優比較困難的場合,可以直接使用這種自適應的方式,僅指定虛擬機器的最大堆、目標的吞吐量 (GCTimeRatio) 和停頓時間 (MaxGCPauseMills),讓虛擬機器自己完成調優工作。

-SurvivorRattio: 新生代Eden區域與Survivor區域的容量比值,預設為8,代表Eden: Suvivor= 8: 1。

-XX:ParallelGCThreads:設定用於垃圾回收的執行緒數。通常情況下可以和 CPU 數量相等。但在 CPU 數量比較多的情況下,設定相對較小的數值也是合理的。

-XX:MaxGCPauseMills:設定最大垃圾收集停頓時間。它的值是一個大於 0 的整數。收集器在工作時,會調整 Java 堆大小或者其他一些引數,儘可能地把停頓時間控制在 MaxGCPauseMills 以內。

-XX:GCTimeRatio:設定吞吐量大小,它的值是一個 0-100 之間的整數。假設 GCTimeRatio 的值為 n,那麼系統將花費不超過 1/(1+n) 的時間