JVM調優淺談
1.資料型別
java虛擬機器中,資料型別可以分為兩類:基本型別和引用型別。
基本型別的變數儲存原始值,即:它代表的值就是數值本身,而引用型別的變數儲存引用值。
“引用值”代表了某個物件的引用,而不是物件本身,物件本身存放在這個引用值所表示的地址的位置。
基本型別包括:byte、short、int、long、char、float、double、boolean
引用型別包括:類型別、介面型別和陣列
複製程式碼
byte
1B(8位)
-128 ~ 127
0
short
2B(16位)
-215 ~ 215-1
0
Int
4B(32位)
-231 ~ 231-1
0
long
8B(64位)
-263 ~ 263-1
0
char
2B(16位)
0 ~ 216-1
\U0000
float
4B(32位)
1.4013E-45 ~3.4028E+38
0.0F
double
8B(64位)
4.9E-324 ~1.7977E+308
0.0D
boolean
1B(8位)
True, false
false
-
堆(heap)與棧(stack)
堆和棧是程式執行的關鍵,很有必要它他們的關係說清楚。
在java中,Main函式就是棧的起始點,也是程式的起始點。程式要執行總是有一個起點的(程式執行的入口)。
概括:
1 棧是執行時的單位 , 而堆是儲存的單元。
2 棧解決程式的執行問題,即程式如何執行,或者說如何處理資料,
堆解決的是資料儲存的問題,即資料怎麼放,放在哪兒。
複製程式碼
在java中一個執行緒就會相應有一個執行緒棧與之對應,這點很容易理解,因為不同的執行緒執行邏輯有所不同,因此需要一個獨立的執行緒棧。
而堆則是所有執行緒共享的。
疑問一:為什麼要把堆和棧區分出來呢?棧中不是也可以儲存資料嗎?
1. 從軟體設計的角度看,棧代表了處理邏輯,而堆代表了資料。這樣分開,使得處理邏輯更為清晰。分而治之的思想。
這種隔離、模組化的思想在軟體設計的方方面面都有體現。
2.堆與棧的分離,使得堆中的內容可以被多個棧共享(也可以理解為多個執行緒訪問同一個物件)。
好處: a.提供了一種有效的資料互動方式(如:共享記憶體)
b.堆中的共享常量和快取可以被所有棧訪問,節省了空間。
3. 棧因為執行時的需要,比如儲存系統執行的上下文,需要進行地址段的劃分。
由於棧只能向上增長,因此就會限制住棧儲存內容的能力,
而堆不同,堆中的物件是可以根據需要動態增長的,
因此棧和堆的拆分使得動態增長成為可能,相應棧中只需記錄堆中的一個地址即可。
4. 物件導向就是堆和棧的完美結合。
其實,物件導向方式的程式與以前結構化的程式在執行上沒有任何區別。
但是,物件導向的引入,使得對待問題的思考方式發生了改變,而更接近於自然方式的思考。
當我們把物件拆開,你會發現,物件的屬性其實就是資料,存放在堆中;
而物件的行為(方法),就是執行邏輯,放在棧中。
我們在編寫物件的時候,其實就是編寫了資料結構,也編寫了處理資料的邏輯。不得不承認,物件導向的設計,確實很美。
複製程式碼
疑問二: 堆中存什麼?棧中存什麼?
1. 棧儲存的資訊都是跟當前執行緒(或程式)相關的資訊。(區域性變數、程式執行狀態、方法、方法返回值)等,
棧中存的是基本資料型別和堆中物件的引用。一個物件的大小是不可估計的,或者說是可以動態變化的,但是
在棧中,一個物件只對應了一個4byte的引用(堆疊分離的好處)。
2. 堆只負責儲存物件資訊。
複製程式碼
疑問三: 為什麼不把基本型別放堆中呢?
1. 其佔用的空間一般是1~8個位元組---需要空間比較少,
2.而且因為是基本型別,所以不會出現動態增長的情況---長度固定,因此棧中儲存就夠了,如果把它存在堆中是沒有什麼意義的(還會浪費空間,後面說明??)。
複製程式碼
疑問四: java中的引數傳遞是傳值呢?還是傳引用?
物件傳遞是引用值傳遞,原始型別資料傳遞是值傳遞
實際上這個傳入函式的值是物件引用的拷貝,即傳遞的是引用的地址值,所以還是按值傳遞
tips:
堆和棧中,棧是程式執行最根本的東西。程式執行可以沒有堆,但是不能沒有棧。
而堆是為棧進行資料儲存服務的,說白了堆就是一塊共享的記憶體。
不過,正是因為堆和棧的分離的思想,才使得java的垃圾回收成為可能。
java中,棧的大小通過-Xss來設定,當棧中儲存的資料比較多時,需要適當調大這個值,否則會出現 java.lang.StackOverflowError異常。
常見的出現這個異常的是無法返回的遞迴,因為此時棧中儲存的資訊都是方法返回的記錄點。
複製程式碼
疑問五: java物件的大小如何計算?
基本資料型別的大小是固定的,這裡就不多說了,對於非基本型別的java物件,其大小就值得商討。
在java中,一個空Object物件的大小是8byte,這個大小隻是儲存堆中一個沒有任何屬性的物件的大小。看看下面語句:
Object ob = new Object();
這樣在程式中完成了一個java物件的宣告,但是它所佔的空間為:4byte+8byte。
複製程式碼
(4byte是上面部分所說的java棧中儲存引用的所需要空間,而那8byte則是java堆中物件的資訊)。
因為所有的java非基本型別的物件都需要預設繼承Object物件,因此不論什麼樣的java物件,其大小都必須是大於8byte。
有了Object物件的大小,我們就可以計算其他物件的大小了。
其大小為:空物件大小(8byte)+int大小(4byte)+Boolea大小(1byte)+空Object引用的大小(4byte)=17byte。
但是因為java在對物件記憶體分配時都是以8的整數倍來分的,因此大於17byte的最接近8的整數倍的是24,因此此物件的大小為24byte。
這裡需要注意一下基本型別的包裝型別的大小。因為這種包裝型別已經成為物件了,因此需要把它們作為物件來看待。
包裝型別的大小至少是12byte(宣告一個空Object至少需要的空間),而且12byte沒有包含任何有效資訊,
同時,因為java物件大小是8的整數倍,因此一個基本型別包裝類的大小至少是16byte。
這個記憶體佔用是很恐怖的,它是使用基本型別的N倍(N>2),這些型別的記憶體佔用更是誇張。因此,可能的話應儘量少使用包裝類。
在JDK5.0以後,因為加入了自動型別裝換,因此,java虛擬機器會在儲存方面進行相應的優化。
複製程式碼
-
引用型別
物件引用型別分為強引用、軟引用、弱引用和虛引用
強引用:我們一般宣告物件時虛擬機器生成的引用,
強引用環境下,垃圾回收時需要嚴格判斷當前物件是否被強引用,如果被強引用,則不會被垃圾回收。 eg: Sample sample = new Sample(); 建立一個物件,new出來的物件都是分配在java堆中的 複製程式碼
軟引用:一般被作為快取來使用。
與強引用的區別是,軟引用在垃圾回收時,虛擬機器會根據當前系統的剩餘記憶體來決定是否對軟引用進行回收。 如果剩餘記憶體比較緊張,則虛擬機器會回收軟引用所引用的空間,如果剩餘記憶體相對富裕,則不會進行回收。 換句話說,虛擬機器在發生OutOfMemory時,肯定是沒有軟引用存在的。 複製程式碼
弱引用:弱引用與軟引用類似,都是作為快取來使用。
但與軟引用不同,弱引用在進行垃圾回收時,是一定會被回收掉的, 因此其生命週期只存在於一個垃圾回收週期內。 虛引用 :顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定物件的生命週期。 如果一個物件僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。 虛引用主要用來跟蹤物件被垃圾回收器回收的活動。 複製程式碼
虛引用與軟引用和弱引用的一個區別在於: 虛引用必須和引用佇列(ReferenceQueue)聯合使用。 當垃圾回收器準備回收一個物件時,如果發現它還有虛引用,就會在回收物件的記憶體之前,把這個虛引用加入到與之關聯的引用佇列中。 ReferenceQueue queue = new ReferenceQueue (); PhantomReference pr = new PhantomReference (object, queue); 程式可以通過判斷引用佇列中是否已經加入了虛引用,來了解被引用的物件是否將要被垃圾回收。 如果程式發現某個虛引用已經被加入到引用佇列,那麼就可以在所引用的物件的記憶體被回收之前採取必要的行動。
-
垃圾回收演算法
a) 按照基本回收策略分
a1. 引用計數(Reference Counting) 比較古老的回收演算法。原理是此物件有一個引用,即增加一個計數,刪除一個引用則減少一個計數。 垃圾回收時,只收集計數為0的物件。此演算法最致命的是無法處理迴圈引用的問題。 a2. 標記-清除(Mark-Sweep) 此演算法執行分兩階段: 第一階段從引用根節點開始標記所有被引用的物件, 第二階段遍歷整個堆,把未標記的物件清除。 此演算法需要暫停整個應用,同時,會產生記憶體碎片。 a3. 複製(Copying) 此演算法把記憶體空間劃分為兩個相等的區域,每次只是用其中一個區域。 垃圾回收時,遍歷當前使用區域,把正在使用中的物件複製到另外一個區域中。 此演算法每次只處理正在使用中的物件,因此複製成本比較小,同時複製過去以後還能進行相應的記憶體整理,不會出現“碎片”問題。 當然,此演算法的缺點也是比較明顯的,就是需要兩倍記憶體空間。 複製程式碼
a4. 標記-整理(Mark-Compact)
此演算法結合了“標記-清除”和“複製”兩個演算法的優點。 也是分兩個階段,第一階段從根節點開始標記所有被引用物件, 第二階段遍歷整個堆,清除未標記物件並且把存活物件“壓縮”到堆中的其中一塊,按順序排放。 此演算法避免了“標記-清除”的碎片問題,同時也避免了“複製”演算法的空間問題。 複製程式碼
b) 按分割槽對待的方式分
增量收集(Incremental Collecting): 實時垃圾回收演算法,即:在應用進行的同時進行垃圾回收。不知道什麼原因JDK5.0中的收集器沒有使用這種演算法。 分代收集(Generational Collecting): 基於對物件生命週期分析後得出的垃圾回收演算法。把物件分為年輕代、年老代、持久代, 對不同生命週期的物件使用不同的演算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此演算法的。 複製程式碼
c) 按系統執行緒分
序列收集: 使用單執行緒處理所有垃圾回收工作,因為無需多執行緒互動,實現容易,而且效率比較高。 但是,其侷限性也比較明顯,即無法使用多處理器的優勢,所以此收集適合單處理器機器。 當然,此收集器也可以用在小資料量(100M左右)情況下的多處理器機器上。 並行收集: 並行收集使用多執行緒處理垃圾回收工作,因而速度快,效率高。 而且理論上CPU數目越多,越能體現出並行收集器的優勢。 併發收集: GC執行緒和應用執行緒大部分時間是併發執行,只是在初始標記(initial mark)和二次標記(remark)時需要stop-the-world, 這可以大大縮短停頓時間(pause time),所以適用於響應時間優先的應用,減少使用者等待時間。 由於GC是和應用執行緒併發執行,只有在多CPU場景下才能發揮其價值,在執行過程中還會產生新的垃圾floating garbage, 如果等空間滿了再開始GC,那這些新產生的垃圾就沒地方放了,這時就會啟動一次序列GC,等待時間將會很長,所以要在空間還未滿時就要啟動GC。 mark和sweep操作會引起很多碎片,所以間隔一段時間需要整理整個空間,否則遇到大物件,沒有連續空間也會啟動一次序列GC。 採用此收集器(如tenured generation),收集頻率不能大,否則會影響到cpu的利用率,進而影響吞吐量。 複製程式碼
-
如何區分垃圾
上面說到的“引用計數”法,通過統計控制生成物件和刪除物件的引用數來判斷。 垃圾回收程式收集計數為0的物件即可。但是這種方法無法解決迴圈引用。所以,後來實現的垃圾判斷演算法中,都是從程式執行的根節點出發,遍歷整個物件引用,查詢存活的物件,那麼在這種方式的實現中,垃圾回收從哪兒開始的呢?即,從哪兒開始查詢哪些物件是正在被當前系統使用的,上面分析的堆和棧的區別,其中棧是真正進行程式執行的地方,所以要獲取哪些物件正在被使用,則需要從java棧開始。同時,一個棧是與一個執行緒對應的,因此,如果有多個執行緒的話,則必須對這些執行緒對應的所有的棧進行檢查。 同時,除了棧外,還有系統執行時的暫存器等,也是儲存程式執行資料的。這樣,以棧或暫存器中的引用為起點,我們可以找到堆中的物件,又從這些物件找到對堆中其它物件的引用,這種引用逐步擴充套件,最終以null引用或者基本型別結束,這樣就形成了一顆以java棧中引用所對應的物件為根節點的一顆物件樹,如果棧中有多個引用,則最終會形成多顆物件樹。在這些物件樹上的物件,都是當前系統執行所需要的物件,不能被垃圾回收,而其他剩餘物件,則可以視為無法被引用到的物件,可以被當做垃圾進行回收。 因此,垃圾回收的起點是一些根物件(java棧、靜態變數、暫存器...)。而最簡單的java棧就是java程式執行的main函式。這種回收方式,也是上面提到的“標記-清除”的回收方式。 如何處理碎片 由於不同java物件存活時間是不一定的,因此,在程式執行一段時間以後,如果不進行記憶體整理,就會出現零散的記憶體碎片。碎片最直接的問題就是會導致無法分配大塊的記憶體空間,以及程式執行效率降低。所以,在上面提到的基本垃圾回收演算法中 ,“複製”方式和“標記-整理”方式,都可以解決碎片的問題。 如何解決同時存在的物件建立和物件回收問題 垃圾回收執行緒是回收記憶體的,而程式執行執行緒則是消耗(或分配)記憶體的,一個回收記憶體,一個分配記憶體,從這點看,兩者是矛盾的。因此,在現有的垃圾回收方式中,要進行垃圾回收前,一般都需要暫停整個應用(即:暫停記憶體的分配),然後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,而且最有效的解決二者矛盾的方式。 但是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,相應應用暫停的時間也會相應的增大。一些相應時間要求很高的應用,比如最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就很有可能超過這個限制,在這種情況下,垃圾回收將會成為系統執行的一個瓶頸。為解決這種矛盾,有了併發垃圾回收演算法,使用這種演算法,垃圾回收執行緒與程式執行執行緒同時執行。在這種方式下,解決了暫停的問題,但是因為需要在新生成物件的同時又要回收物件,演算法複雜性會大大增加,系統的處理能力也會相應降低,同時,“碎片”問題將會比較難解決,以後研究的重點!!!!!!。 為什麼要分代 分代的垃圾回收策略,是基於這樣一個事實:不同的物件的生命週期是不一樣的。因此,不同宣告週期的物件可以採取不同的收集方式,以便提高回收效率。 在Java程式執行的過程中,會產生大量的物件,其中有些物件是與業務資訊相關,比如Http請求中的Session物件、執行緒、Socket連線,這類物件跟業務直接掛鉤,因此生命週期比較長。但是還有一些物件,主要是程式執行過程中生成的臨時變數,這些物件生命週期比較短,比如:String物件,由於其不變類的特性,系統會產生大量的這些物件,有些物件甚至只用一次即可回收。 是想,在不進行物件存活時間區分的情況下,每次垃圾回收都是對整個堆空間進行回收,花費時間相對會長,同時,因為每次回收都需要遍歷所有存活物件,但實際上,對於生命週期長的物件而言,這種遍歷是沒有效果的,因為可能進行了很多次遍歷,但是它們依舊存在。因此,分代垃圾回收採用分治的思想,進行代的劃分,把不同生命週期的物件放在不同代上,不同代上採用最適合它的垃圾回收方式進行回收。 如何分代 如圖所示: 虛擬機器中共劃分了三個代:年輕代(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 GC 和 Full 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調優的過程中,很大一部分工作就是對於 Full GC 的調節。 複製程式碼
有如下原因可能導致Full GC: . 年老代(Tenured)被寫滿
. 持久代(Perm)被寫滿
. System.gc()被顯式呼叫
. 上一次GC之後Heap的各域分配策略動態變化
分代垃圾回收流程示意
選擇合適的垃圾收集演算法
序列收集器
用單執行緒處理所有垃圾回收工作,因為無需多執行緒互動,所有效率比較高。但是,也無法使用多處理器的優勢,所以此收集器適合單處理器機器。當然,此收集器也可以用在小資料量(100M左右)情況下的多處理器機器上。可以使用 -XX:+UseSerialGC開啟。
並行收集器
對年輕代進行並行垃圾回收,因此可以減少垃圾回收時間。一般在多執行緒多處理器機器上使用。使用 -XX:+UseParallelGC 開啟。並行收集器在 J2SE5.0第六6更新上引入,在java SE6.0中進行了增強 --- 可以對年老代進行並行收集。如果年老代不使用並行收集的話,預設是使用單執行緒進行垃圾回收,因此會制約擴充套件能力。使用 -XX:+UseParallelOldGC開啟。
使用 -XX:ParallelGCThreads = <N> 設定並行垃圾回收的執行緒數。此值可以設定與機器處理器數量相等。
此收集器可以進行如下配置:
最大垃圾回收暫停:指定垃圾回收時的最長暫停時間,通過-XX:MaxGCPauseMillis = <N>指定。<N> 為毫秒,如果指定了此值的話,堆大小和垃圾回收相關引數會進行調整以達到指定值。設定此值可能會減少應用的吞吐量。
吞吐量:吞吐量為垃圾回收時間與非垃圾回收時間的比值,通過-XX:GCTimeRatio = <N> 來設定,公式為 1/(1 + N)。例如,-XX:GCTimeRatio = 19時,表示5%的時間用於垃圾回收。預設情況為99,即1%的時間用於垃圾回收。
併發收集器
可以保證大部分工作都併發進行(應用不停止),垃圾回收只暫停很少的時間,此收集器適合對響應時間要求比較高的中、大規模應用。使用 -XX:+UseConcMarkSweepGC開啟。
併發收集器主要減少年老代的暫停時間,它在應用不停止的情況下使用獨立的垃圾回收執行緒,跟蹤可達物件。在每個年老代垃圾回收週期中,在收集初期併發收集器會對整個應用進行簡短的暫停。在收集中還會再暫停一次。第二次暫停會比第一次稍長,在此過程中多個執行緒同時進行垃圾回收工作。
併發收集器使用處理器換來短暫的停頓時間。在一個N個處理器的系統上,併發收集部分使用 k/N 個可用處理器進行回收,一般情況下 1 <= k <= N / 4。
在只有一個處理器的主機上使用併發收集器,設定為 incremental mode 模式也可獲得較短的停頓時間。
浮動垃圾:由於在應用執行的同時進行垃圾回收,所以有些垃圾可能在垃圾回收進行完成時產生,這樣就造成了“Floating Garbage”,這些垃圾需要在下次垃圾回收週期時才能回收掉。所以,併發收集器一般需要20%的預留空間用於這些浮動垃圾。
Concurrent Mode Failure:併發收集器在應用執行時進行收集,所以需要保證堆在垃圾回收的這段時間有足夠的空間供程式使用,否則,垃圾回收還未完成,堆空間先滿了。這種情況下將會發生“併發模式失敗”,此時整個應用將會暫停,進行垃圾回收。
啟動併發收集器:因為併發收集在應用執行時進行收集,所以必須保證收集完成之前有足夠的記憶體空間供程式使用,否則會出現“Concurrent Mode Failure”。通過設定 -XX:CMSInitiatingOccupancyFraction = <N> 指定還有多少剩餘堆是開始執行併發收集。
小結
序列處理器:
-- 適用情況:資料量比較小(100M左右),單處理器下並且對相應時間無要求的應用。
-- 缺點:只能用於小型應用。
並行處理器:
-- 適用情況:“對吞吐量有高要求”,多CPU,對應用過響應時間無要求的中、大型應用。舉例:後臺處理、科學計算。
-- 缺點:垃圾收集過程中應用響應時間可能加長。
併發處理器:
-- 適用情況:“對響應時間有高要求”,多CPU,對應用響應時間有較高要求的中、大型應用。舉例:Web伺服器/應用伺服器、電信交換、整合開發環境。
以下配置主要針對分代垃圾回收演算法而言。
堆大小設定
年輕代的設定很關鍵
JVM中最大堆大小有三方面限制:相關作業系統的資料模型(32-bit 還是64-bit)限制;系統的可用虛擬記憶體限制;系統的可用實體記憶體限制。32位系統下,一般限制在1.5G~2G;64位作業系統對記憶體無限制。在Windows Server 2003系統,3.5G實體記憶體,JDK5.0下測試,最大可設定為1478m。
典型設定:
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-Xmx3550m:設定JVM最大可用記憶體為3550m。
-Xms3550m:設定JVM初始記憶體為3550m。此值可以設定與 -Xmx 相同,以避免每次垃圾回收完成後JVM重新分配記憶體。
-Xmn2g:設定年輕代大小為2G。整個堆大小=年輕代大小+年老代大小+持久代大小。持久代一般固定大小為64m,所以增大年輕代後,將會減小年老代大小。此值對系統效能影響較大,Sun官方推薦配置為整個堆的3/8。
-Xss128k:設定每個執行緒的堆疊大小。JDK5.0以後每個執行緒堆疊大小為1M,以前每個執行緒堆疊大小為256k。根據應用的執行緒所需記憶體大小進行調整。在相同實體記憶體下,減小這個值能生成更多的執行緒。但是作業系統對一個程式內的執行緒數還是有限制的,不能無限生成,經驗值在3000~5000左右。
java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
-XX:NewRatio=4:設定年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)。設定為4,則年輕代與年老代所佔比值為1:4,年輕代佔整個堆疊的1/5。
-XX:SurvivorRatio=4:設定年輕代中Eden區與Survivor區的大小比值。設定為4,則兩個Survivor區與一個Eden區的比值為2:4,一個Survivor區佔整個年輕代的1/6。
-XX:MaxPermSize=16m:設定持久代大小為16m。
-XX:MaxTenuringThreshold=0:設定垃圾最大年齡。如果設定為0的話,則年輕代物件不經過Survivor區,直接進入年老代。對於年老代比較多的應用,可以提高效率。如果此值設定為一個較大值,則年輕代物件會在Survivor區進行多次複製,這樣可以增加物件在年輕代的存活時間,增加在年輕代被回收的概率。
回收器選擇
JVM給了三種選擇:序列收集器、並行收集器、併發收集器,但是序列收集器只適用於小資料量的情況,所以這裡的選擇主要針對並行收集器和併發收集器。預設情況下,JDK5.0以前都是使用序列收集器,如果想使用其他收集器需要在啟動的時候加入相應引數。JDK5.0以後,JVM會根據當前系統配置進行判斷。
吞吐量優先的並行收集器
如上文所述,並行收集器主要以到達一定的吞吐量為目標,適用於科學計算和後臺處理等。
典型配置:
java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
-XX:+UseParallelGC:選擇垃圾收集器為並行收集器。此配置僅對年輕代有效。即上述配置下,年輕代使用併發收集,而年老代仍舊使用序列收集。
-XX:+ParallelGCThreads=20:配置並行收集器的執行緒數,即:同時多少個執行緒一起進行垃圾回收。此值最好配置與處理器數目相等。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
-XX:+UseParallelOldGC:配置年老代垃圾收集方式為並行收集。JDK6.0支援對年老代並行收集。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=100:設定每次年輕代垃圾回收的最長時間,如果無法滿足此時間,JVM會自動調整年輕代大小,以滿足此值。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
-XX:+UseAdaptiveSizePolicy:設定此選項後,並行收集器會自動選擇年輕代區大小和相應的Survivor區比例,以達到目標系統規定的最低響應時間或者收集頻率等,此值建議使用並行收集器時,一直開啟。
響應時間優先的併發收集器
如上文所述,併發收集器主要是保證系統的響應時間,減少垃圾收集時的停頓時間。適用於應用伺服器、電信領域等。
典型配置:
java -Xmx3550m -Xms3550 -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC:設定年老代為併發收集。測試中配置這個以後,-XX:NewRatio=4的配置失效了,原因不明。所以,此時年輕代大小最好用-Xmn設定。
-XX:+UseParNewGC:設定年輕代為並行收集。可與CMS收集同時使用。JDK5.0以上,JVM會根據系統配置自行設定,所以無需再設定此值。
java -Xmx3550m -Xms3550 -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction:由於併發收集器不對記憶體空間進行壓縮、整理,所以執行一段時間後會產生“碎片”,使得執行效率降低。此值設定執行多少次GC以後對記憶體空間進行壓縮、整理。
-XX:+UseCMSCompactAtFullCollection:開啟對年老代的壓縮。可能會影響效能,但是可以消除碎片。
常見配置彙總
堆設定
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:設定年輕代大小
-XX:NewRatio=n:設定年輕代和年老代的比值。如:為3,表示年輕代與年老代比值為1:3,表示Eden:Survivor=3:2,一個Survivor區佔整個年輕代的1/5。
-XX:MaxPermSize=n:設定持久代大小
收集器設定
-XX:+UseSerialGC:設定序列收集器
-XX:+UseParallelGC:設定並行收集器
-XX:+UseParalledlOldGC:設定並行年老代收集器
-XX:+UseConcMarkSweepGC:設定併發收集器
垃圾回收統計資訊
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
並行收集器設定
-XX:ParallelGCThreads=n:設定並行收集器收集時使用的CPU數。並行收集執行緒數。
-XX:MaxGCPauseMillis=n:設定並行收集最大暫停時間
-XX:GCTimeRatio=n:設定垃圾回收時間佔程式執行時間的百分比。公式為1/(1+N)
併發收集器設定
-XX:+CMSIncrementalMode:設定為增量模式。適用於單CPU情況。
-XX:+ParallelGCThreads=n:設定併發收集器年輕代收集方式為並行收集時,使用的CPU數。並行收集執行緒數。
複製程式碼