JVM的基本概念與維護調優

1、概念

數據類型

java虛擬機中,數據類型能夠分爲兩類:html

基本類型
引用類型

基本類型的變量保存原始值,即:他表明的值就是數值自己;而引用類型的變量保存引用值。
基本類型包括:byte,short,int,long,float,double,Booleanjava

「引用值」表明了某個對象的引用,而不是對象自己,對象自己存放在這個引用值所表示的地址位置。
引用類型包括:類的類型,接口類型和數組算法

堆與棧

棧是運行時單位
堆是存儲單位

棧解決程序的運行問題,即程序如何執行,或者說如何處理數據;堆解決的是數據存儲問題,即數據怎麼放,放在哪數組

在java中一個線程就會相應有一個線程棧與之對應,這點很容易理解,由於不一樣的線程執行邏輯有所不一樣,所以須要一個獨立的線程棧。而堆則是全部線程共享的。棧由於是運行單位,所以裏面存儲的信息都是跟當前線程相關的信息。包括局部變量、程序運行狀態、方法返回值等;而堆只負責存儲對象信息緩存

爲何要把堆和棧區分出來呢?棧中不是也能夠存儲數據嗎?

  1. 從software設計角度來看,棧表明了處理邏輯,而堆表明了數據。這樣分開,使得處理邏輯更爲清晰。分而治之思想。這種隔離,模塊化思想在軟件設計的方面都有體現。
  2. 堆與棧的分離,使得堆中的內容能夠被多個棧共享(也能夠理解爲多個線程訪問同一個對象)。這種共享的收益不少的。一方面這種共享提供了一種有效的數據交互方式(共享內存),另外一方面,堆中的共享常量和緩存能夠被全部棧訪問,節省了空間
  3. 棧由於運行時的須要,好比保存系統運行上下文,須要進行地址段的劃分。因爲棧只能向上增加,所以就會限制住棧存儲內容的能力。而堆不一樣,堆中的對象是能夠根據須要動態增加的,所以棧和堆的拆分,使得動態增加成爲可能,相應棧中只須要記錄堆中的一個地址便可
  4. 面向對象就是堆和棧的完美結合。其實,面向對象方式的程序與之前結構化的程序在執行上沒有任何區別。可是,面向對象的引入,使得對待問題的思考方式發生了改變,而更接近於天然方式的思考。當咱們把對象拆開,你會發現,對象的屬性其實就是數據,存放在堆中;而對象的行爲(方法),就是運行邏輯,放在棧中。咱們在編寫對象的時候,其實即編寫了數據結構,也編寫了處理數據的邏輯。不得不認可,面向對象的設計,確實完美。

堆中存什麼?棧中存什麼?

堆中存對象
棧中存最基本數據類型和堆中對象的引用

一個對象的大小是不可估計的,或者說是可估計的,或者說是能夠動態改變的,但在棧中,一個對象只對應4byte的引用服務器

爲何不把基本類型放堆中呢?

由於其佔用的空間通常是1~8個字節,須要空間比較少,並且由於是基本類型,因此不會出現動態增加的狀況,長度固定,所以棧中存儲就夠了,若是把他存在堆中是沒什麼意義的。能夠說是浪費。基本類型和對象的引用都是在存放在棧中,並且都是幾個字節的一個數,所以在程序運行時,他們的處理方式是統一的。可是基本類型、對象引用和對象自己就是有所區別的,由於一個棧中的數據一個是堆中的數據。最多見的一個問題就是,java中參數傳遞時的問題。數據結構

java中的參數傳遞時傳值呢?仍是傳引用?

  1. 不要試圖與C進行類比,java中沒有指針的概念
  2. 程序運行永遠都是在棧中進行的,於是參數傳遞時,只存在傳遞基本類型和對象引用的問題。不會直接傳對象自己。

java在方法調用傳遞參數時,由於沒有指針,因此它都是進行傳值調用。所以,不少書裏面都說java是進行傳值調用,這點沒有問題,並且也簡化了C中的複雜性。多線程

可是傳引用的錯覺是如何形成的呢?

在運行棧中,基本類型和引用的處理都是同樣的,都是傳值,因此,若是是傳引用的方法調用,也同時能夠理解爲「傳引用值」的傳值調用,即引用的處理跟基本類型是徹底不同的。可是當進入被調用方法時,被傳遞的這個引用的值,被程序解釋到堆中的對象,這個時候纔對應到真正的對象,若是此時進行修改,修改的是引用對應的對象,而不是引用自己,即修改的是堆中的數據。因此這個修改是能夠保持的了。併發

對象,是由基本類型組成的。能夠把一個對象看作成一棵樹,對象的屬性若是仍是對象,則仍是一棵樹(即非葉子節點),基本類型則爲樹的葉子節點。程序參數傳遞時,被傳遞的值自己都是不能進行修改的,可是,若是這個值是一個非葉子節點(即一個對象引用),則能夠修改這個節點下面的全部內容。jvm

堆和棧中,棧是程序運行最根本的東西。程序運行能夠沒有堆,但不能沒有棧。而堆是爲棧進行數據存儲服務,說白了堆就是一塊共享的內存。不過,正是由於堆和棧的分離的思想,才使得java的垃圾回收成爲可能

java中,棧的大小經過-Xss來設置,當棧中存儲的數據比較多時,須要適當調大這個值,不然會出現java.lang.StackOverflowError異常。常見的出現這個異常的是沒法返回的遞歸,由於此時棧中保存的信息都是方法返回的記錄點。

引用類型

對象引用類型分爲:

強引用
軟引用
弱引用和虛引用

強引用:就是咱們通常聲明對象是時虛擬機生成的引用,強引用環境下,垃圾回收時須要嚴格判斷當前對象是否被強引用,若是被強引用,則不會被垃圾回收

軟引用:軟引用通常被作爲緩存來使用。與強引用的區別是,軟引用在垃圾回收時,虛擬機會根據當前系統的剩餘內存來決定是否對軟引用進行回收。若是剩餘內存比較緊張,則虛擬機會回收軟引用所引用的空間:若是剩餘內存相對富裕,則不會進行回收。換句話說,虛擬機在發生OutOfMemory時,確定是沒有軟引用存在的

弱引用:弱引用與軟引用相似,都是做爲緩存來使用。但與軟引用不一樣,弱引用在進行垃圾回收時,是必定會被回收掉的,所以其生命週期只存在於一個垃圾回收週期內。

強引用是咱們系統通常在使用時都是用的強引用。而「軟引用」和「弱引用」比較少見。他們通常被做爲緩存使用,並且通常是在內存大小比較受限的狀況下作爲緩存。由於若是內存足夠大的話,能夠直接使用強引用做爲緩存便可,同時可控性更高。於是,他們常見的是被使用在桌面應用系統的緩存

2、基本垃圾回收算法

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

按系統線程分

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

並行收集:並行收集使用多線程處理垃圾回收工做,於是速度快,效率高。並且理論上CPU數目越多,越能體現出並行收集器的優點

併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工做時,須要暫時中止整個運行環境,而只有垃圾回收程序在運行,所以,系統在垃圾回收時會有明顯的暫停,並且暫停時間會由於堆越大而越長

3、垃圾回收面臨的問題

如何區分垃圾?

上面說到的「引用計數」法,經過統計控制生成對象和刪除對象時的引用數來判斷。垃圾回收程序收集計數爲0的對象便可。可是這種方法沒法解決循環引用。因此,後來實現的垃圾判斷算法中,都是從程序運行的根節點出發,遍歷整個對象引用,查找存活的對象。那麼在這種方式的實現中,垃圾回收從哪兒開始的呢?從哪兒開始查找哪些對象是正在被當前系統使用的,上面分析的堆和棧的區別,其中棧是真正進行程序執行地方,因此要獲取哪些對象正在被使用,則須要從Java棧開始。同時,一個棧是與一個線程對應的,所以,若是有多個線程的話,則必須對這些線程對應的全部的棧進行檢查

同時,除了棧外,還有系統運行時的寄存器等,也是存儲程序運行數據的。這樣,以棧或寄存器中的引用爲起點,咱們能夠找到堆中的對象,又從這些對象找到對堆中其餘對象的引用,這種引用逐步擴展,最終以null引用或者基本類型結束,這樣就造成了一顆以Java棧中引用所對應的對象爲根節點的一顆對象樹,若是棧中有多個引用,則最終會造成多顆對象樹。在這些對象樹上的對象,都是當前系統運行所須要的對象,不能被垃圾回收。而其餘剩餘對象,則能夠視爲沒法被引用到的對象,能夠被當作垃圾進行回收。

所以,垃圾回收的起點是一些根對象(java棧,靜態變量,寄存器)。而最簡單的Java棧就是Java程序執行的main函數。這種回收方式,也是上面提到的「標記-請除」的回收方式。

如何處理碎片?

因爲不一樣Java對象存活時間是不必定的,所以,在程序運行一段時間之後,若是不進行內存整理,就會出現零散的內存碎片。碎片最直接的問題就是會致使沒法分配大塊的內存空間,以及程序運行效率下降。因此,在上面提到的基本垃圾回收算法中,「複製」方式和「標記-整理」方式,均可以解決碎片的問題.

如何解決同時存在的對象建立和對象回收問題?

垃圾回收線程是回收內存的,而程序運行線程則是消耗(或分配)內存的,一個回收內存,一個分配內存,從這點看,二者是矛盾的。所以,在現有的垃圾回收方式中,要進行垃圾回收前,通常都須要暫停整個應用(即暫停內存的分配),而後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,並且最有效的解決一者矛盾的方式。可是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,對應應用暫停的時間也會相應的增大。一些對相應時間要求很高的應用,好比最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就頗有可能超過這個限制,在這種狀況下,垃圾收將會成爲系統運行的一個瓶頸。爲解決這種矛盾,有了併發垃圾回收算法,使用這種算法,垃圾回收線程與程序運行線程同時運行。在這種方式下,解決了暫停的問題,可是由於須要在新生成對象的同時又要回收對象,算法複雜性會大大增長,系統的處理能力也會相應下降,同時,「碎片」問題將會比較難解決

4、分代垃圾回收詳述

爲何要分代?

分代的垃圾回收策略,是基於這樣一個事實:不一樣的對象的生命週期是不同的。所以,不一樣生命週期的對象能夠採起不一樣的收集方式,以便提升回收效率。在Java程序運行的過程當中,會產生大量的對象,其中有些對象是與業務信息相關,好比Http 請求中的Session對象、線程、Socket鏈接,這類對象跟業務直接掛鉤,所以生命的週期比較長,可是還有一些對象,主要是程序運行過程當中生成的臨時變量,這些對象生命週期會比較短,好比:String對象,因爲其不變類的特性,系統會產生大量的這些對象,有些對象甚至只用一次便可回收。

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

虛擬機中的共劃分爲三個代:

年輕代(Young Generation)
年老代(Old Generation)
持久代(Permanent Generation)

其中持久代主要存放的是Java類的類信息,與垃圾收集要收集的Java對象關係不大。年輕代和年老代的劃分是對垃圾收集影響比較大的。

年輕代:全部新生成的對象首先都是放在年輕代的。年輕代的目標就是儘量快速的收集掉那些生命週期短的對象。年輕代分三個區:1個Eden區,2個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:

  1. 年老代(Tenured)被寫滿
  2. 持久代(Perm)被寫滿
  3. System.gc()被顯示調用
  4. 上一次GC以後Heap的個域分配策略動態變化

選擇合適的垃圾收集算法

串行收集器:用單線程處理全部垃圾回收工做,由於無需多線程交互,因此效率比較高。可是,也沒法使用多處理器的優點,因此此收集器適合單處理器機器。固然,此收集器也能夠用在小數據量(100M左右)狀況下的多處理器機器上。可使用-XX:+UseSerialGC打開。

並行收集器:對年輕代進行井行垃圾回收,所以能夠減小垃圾回收時間。通常在多線程多處理器機器上使用。使用-XX:+UseParallelGC打開,並行收集器在J2SE5.0第六6更新上引入,在JavaSE6.0中進行了加強,能夠對年老代進行並行收集,若是年老代不使用併發收集的話,默認是使用單線程進行垃圾回收,所以會制約擴展能力。使用XX:+UseParalelOldGC打開並行收集器收集年老代,使用-XX:ParalelGCThreads=<N>設置並行垃圾回收的線程數。

此收集器能夠進行以下配置:
最大垃圾回收暫停:指定垃圾回的時的最長暫停時間,通討-XX:MaxGCPauseMilis=<N>指定。<N>爲毫秒,若是指定了此值的話,堆大小和垃圾回收相關參數會進行調整以達到指定值。設定此值可能會減小應用的吞吐量。
吞吐量:吞吐量爲垃圾回收時間與非垃圾回收時間的比值,經過-XX:GCTimeRatio=<N>來設定,公式爲1/(1+N)。例如,-XX:GCTimeRatio=19時,表示5%的時間用於垃圾回收。默認狀況爲99,即1%的時間用於垃圾回收。

併發收集器能夠保證大部分工做都併發進行(應用不中止),垃圾回收只暫停不多的時間,此收集器適合對響應時間要求比較高的中、大規模的應用。使用-XX:+UseConcMarkSweepGC打開。

併發收集器主要減小年老代的暫停時間,他在應用不中止的狀況下使用獨立的垃圾回收線程,跟蹤可達對象。在每一個年老代垃圾回收週期中,在收集初期併發收集器會對整個應用進行簡短的暫停,在收集中還會再暫停一次。第二次暫停會比第一次稍長,在此過程當中多個線程同時進行垃圾回收工做。併發收集器使用處理器短暫的停頓時間。在一個N個處理器的系統上,併發收集部分使用KWN個可用處理器進行回收,通常狀況下1<=K<=N/4。在只有一個處理器的主機上使用併發收集器,設置爲incrementalmode 模式也可得到較短的停頓時間。

浮動垃圾:因爲在應用運行的同時進行垃圾回收,因此有些垃圾可能在拉圾回收進行完成時產生,這樣就形成了「Floating Garbage」,這些垃圾須要在下次拉圾回收週期時才能回收掉,因此,併發收集器通常須要20%的預留空間用於這些浮動垃圾。

Concurrent Mode Failure:併發收集器在應用運行時進行收集,因此須要保證堆在垃圾回收的這段時間有足夠的空間供程序使用,不然,垃圾回收還未完成,堆空間先滿了。這種狀況下將會發生「併發模式失敗」,此時整個應用將會暫停,進行垃極回收。

啓動併發收集器:由於併發收集在應用運行時進行收集,因此必須保證收集完成以前足夠的內存空間供程序使用,不然會出現「Concurrent Mode Failure」.經過設置XX:CMSInitiatingOccupancyFraction=<N>指定還有多少剩餘堆時開始執行併發收集。

小結

串行處理器:
適用狀況:數據量比較小(100M左右):單處理器下而且對響應時間無要求的應用。
缺點:只能用於小型應用。

並行處理器:
適用狀況:「對吞吐量有高要求」,多CPU、對應用響應時間花裝求的中、大型應用。舉例:後臺處理、科學計算。
缺點:垃圾收集過程當中應用響應時間可能加長。

併發處理器:
適用狀況:「對響應時間有高要求」,多CPU、對應用響應時間有較高要求的中、大型應用。舉例:Web服務器/應用服務器、電信交換、集成開發環境。

5、常見配置彙總

堆設置

-Xms:初始堆大。
-Xmx:最大堆大小。
初始堆大小與最大堆大小盡可能小於4G。兩值不相等的話,內存地址中會計算會對應用程序產生影響,索性設爲相等。

-XX:NewSize=n:設置年輕代大小。
-XX:NewRatio=n:設置年輕代和年老代的比值。如:爲3,表示年輕代與年老代比值爲1:3,年輕代佔整個年輕代年老代的1/4。
-XX:SurvivorRatio=n:年輕代中Eden區與連個Survivor區的比值。注意Survivor區有兩個。如:3,表示Eden:Survivor=3:2,一個Survivor區佔整個年代的1/5。
-XX:MaxPermSize=n:設置持久代大小。

收集器設置

-XX:+UseSerialGC:設置串行。
-XX:+UseParallelGC:設置並行。
-XX:+UseParalledlOldGC:設置並行年老代。
-XX:+UseConcMarkSweepGC:設置併發。

垃圾回收統計信息

-XX:+PrintGC:輸出GC日誌。
-XX:+PrintGCDetails:輸出GC的詳細日誌。
-XX:+PrintGCTimeStamps:輸出GC的時間戳(以基準時間的形式)。
-Xloggc:filename:日誌文件的輸出路徑。

併發收集器設置

-XX:ParallelGCThreads=n:設置並行收集器收集使用的CPU數。並行收集線程數。
-XX:MaxGCPauseMillis=n:設置並行收集最大暫停時間。
-XX:GCTimeRatio=n:設置垃圾回收時間佔程序運行時間的百分比。公式1/(1+n)。
-XX:+CMSlncrementalMode:設置爲增量模式。適用於單CPU狀況。
-XX:ParallelGCThreads=n:設置併發收集器年期代收集方式,使用的CPU數。並行收集線程。

6、典型配置舉例

堆大小設置

JVM中最大堆大小有三方面限制:相關操做系統的數據模型(32bit仍是64bit)限制;系統的可用虛擬內存限制;系統的可用物理內存限制。32位系統下,通常限制在1.5G~2G;64位操做系統對內存無限制。在Windows Server 2003系統,3.5G物理內存,JDK5.0下測試,最大可設置爲1478m

典型設置:

java -Xms3550m -Xmx3550m -Xmn2G -Xss128k

-Xmx3550m:設置JVM最大可用堆內存。
-Xms3550m:設置JVM初始堆內存爲3550m。此值能夠設置與-Xmx相同,以避免每次垃圾回收完成後JVM從新分配內存
-Xmn2g:設置年輕代大小爲2G。整個堆大小=年輕代大小+年老代大小+持久代大小。持久代通常固定在64m,因此增大年輕代後,將會減少年老代大小。此值對系統性能影響較大,Sun官方推薦配置整個堆的3/8
-Xss128k:設置每一個線程的堆棧大小。JDK5之後每一個線程堆棧大小爲1M,之前每一個線程堆棧大小爲256k。具應用的線程所需內存大小進行調整。在相同的物理內存下,減少這個值能生成更多的線程。可是操做系統對一個進程內的線程數仍是有限制的,不能無限生成,閥值在3000-5000左右。

java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermmSize=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:+UseParalelGC -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:MaxGCPauseMilis=100

-XX:MaxGCPauseMilis=100:設置每次年輕代垃圾回收的最長時間,若是沒法知足此時間,JVM會自動調整大小,以知足此值

java -Xmx 3550m -Xms 3550m -Xmn2g -Xss128k -XX:+UseParalleGC -XX:MaxGCPauseMilis=100 -XX:+UseAdaptiveSizePolicy

-XX:+UseParalleGC:設置此項後,並行收集器會自動選擇年輕代區大小和相應的Surivor區比例,已達到目標系統規定的最低相應時間或者收集頻率等,此值建議使用並行收集器,一直打開。

響應時間優先的併發收集器

如上文所述,併發收集器主要保證系統的響應時間,減小垃圾收集時間的停頓時間。適用於應用服務器、電信領域等。
典型配置:

java-Xmx3550m-Xms3550m-Xmn2g-Xss128k-XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC

-XX:+UseConcMarkSweepGC:設置年老代爲併發收集。測試中配置這個之後,-XX:NewRatio=4的配置失效了,緣由不明。因此,此時年輕代大小最好用-Xmn設置。
XX:+UseParNewGC:設置年輕代爲並行收集,可與CMS收集同時使用JDK5.0以上JVM會根據系統配置自行設置,因此無需再設置此值。

java-Xmx3550m-Xms3550m-Xmn2g-Xss128k-XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection

-XX:CMSFulGCsBeforeCompaction:因爲併發收集器不對內存空間進行壓縮、整理,因此運行一段時間之後會產生「碎片」,使得運行效率下降。此值設置運行多少次GC之後對內存空間進行壓縮、整理
-XX:CMSCompactAtFullCollection:打開對年老代的壓縮。可能會影響性能,可是能夠消除碎片

7、調優總結

年輕代大小選擇

響應時間優先的應用:儘量設大,直到接近系統的最低響應時間限制(根據實際狀況選擇);在此種狀況下,年輕代收集發生的頻率也是最小的。同時,減小到達年老代的對象
吞吐量優先的應用:儘量的設置大,可能到達Gbit的程度。由於對響應時間沒有要求,垃圾收集能夠並行進行,通常適合8CPU以上的應用。

年老代大小選擇

響應時間優先的應用:年老代使用併發收集器,因此其大小須要當心設置,通常要考慮併發會話率和會話持續時間等一些參數。若是堆設置小了,能夠會形成內存碎片、高回收頻率以及應用暫停而使用傳統的標記清除方式:若是堆大了,則須要較長的收集時間。最優化的方案,通常須要參考如下數據得到:

  1. 併發垃圾收集信息
  2. 持久代併發收集次數
  3. 傳統GC信息
  4. 花在年輕代和年老代回收上的時間比例減小年輕代和年老代花費的時間,通常會提升應用的效率

吞吐量優先的應用:通常吞吐量優先的應用都有一個很大的年輕代和一個較小的年老代。緣由是,這樣能夠儘量回收掉大部分短時間對象,減小中期的對象,而年老代盡存放長期存活對象。

較小堆引發的碎片問題

由於年老代的併發收集器使用標記、清除算法,因此不會對堆進行壓縮。當收集器回收時,他會把相鄰的空間進行合併,這樣能夠分配給較大的對象。可是,當堆空間較小時,運行一段時間之後,就會出現「碎片」,若是併發收集器找不到足夠的空間,那麼併發收集器將會中止,而後使用傳統的標記、清除方式進行回收。若是出現「碎片」,可能須要進行以下配置:
-XX:+UseCMSCompactAtFullCollection:使用併發收集器時,開啓對年老代的壓縮。
-XX:CMSFullGCsBeforeCompaction=0:上面配置開啓的狀況下,這裏設置多少次Full GC後,對年老代進行壓縮。

8、常見異常

內存泄漏檢查
內存泄漏通常能夠理解爲系統資源(各方面的資源,堆、棧、線程等)在錯誤使用的狀況下,致使使用完畢的資源沒法回收(或沒有回收),從而致使新的資源分配請求沒法完成,引發系統錯誤。內存泄漏對系統危害比較大,由於他能夠直接致使系統的崩潰。須要區別一下,內存泄漏和系統超負荷二者是有區別的,雖然可能致使的最終果是同樣的。內存泄漏是用完的資源沒有回收引發錯誤,而系統超負荷則是系統確實沒有那麼多資源能夠分配了(其餘的資源都在使用)。

年老代堆空間被佔滿
異常:java.lang.OutOfMemoryError.Java heap space
說明:堆大小與使用堆成鋸齒狀。這是最典型的內存泄漏方式,簡單說就是全部堆空間都被沒法回收的垃圾對象佔滿,虛擬機沒法再在分配新空間。鋸齒狀每一次造成三角的頂點而降低爲回收點,全部谷底部分表示是一次垃圾回收後剩餘的內存。鏈接全部谷底的點,能夠發現一條由底到高的線,這說明,隨時間的推移,系統的堆空間被不斷佔滿,最終會佔滿整個堆空間。所以能夠初步認爲系統內部可能有內存泄漏。(上面的圖僅供示例,在實際狀況下收集數據的時間須要更長,好比幾個小時或者幾天)
解決:經過jmap把可用對象打出來,這種方式解決起來也比較容易,通常就是根據垃極回收先後狀況對比,同時根據對象引用狀況(常見的集合對象引用)分析,基本均可以找到泄漏點。

持久代被佔滿
異常:java.lang.OutOfMemoryError:PermGen space
說明:Perm空間被佔滿。沒法爲新的cdass分配存儲空間而引起的異常。這個異常之前是沒有的,可是在Java反射大量使用的今天這個異常比較常見了。主要緣由就是大量動態反射生成的類不斷被加載,最終致使Perm區被佔滿。
更可怕的是,不一樣的classLoader即使使用了相同的類,可是都會對其進行加載,至關於同一個東西,若是有N個classLoader那麼他將會被加載N次。所以,某些狀況下,這個問題基本視爲無解。固然,存在大量classLoader和大量反射類的狀況其實也很少。
解決:1.-XX:MaxPermSize=16m。2.換用JDK。好比JRocket。

堆棧溢出
異常:java.lang.StackOverflowError
說明:通常就是遞歸沒返回,或者循環調用形成.

線程堆棧滿
異常:Fatal:Stack size too small
說明:java中一個線程的空間大小是有限制的。JDK5.0之後這個值是1M。與這個線程相關的數據會保存在其中。可是當線程空間滿了之後,將會出現上面異常。
解決:增長線程棧大小。-Xss2m。但這個配置沒法解決根本問題,還要看代碼部分是否有形成泄漏的部分。

系統內存被佔滿
異常:java.lang.OutOfMemoryError:unable to create new.native thread
說明:
這個異常是因爲操做系統沒有足夠的資源來產生這個線程形成的。系統建立線程時,除了要在Java堆中分配內存外,操做系統自己也須要分配資源來建立線程。所以,當線程數量大到必定程度之後,堆中或許還有空間,可是操做系統分配不出資源來了,就出現這個異常了。
分配給Java虛擬機的內存越多,系統剩餘的資源就越少,所以,當系統內存固定時,分配給Java虛擬機的內存越多,那麼,系統總共可以產生的線程也就越少,二者成反比的關係。同時,能夠經過修改-Xss來減小分配給單個線程的空間,也能夠增長系統總共內生產的線程數。
解決:1.從新設計系統減小線程數量。2.線程數量不能減小的狀況下,經過-Xss減少單個線程大小。以便能生產更多的線程。

9、JVM調優工具

JVM可視化監控工具
Jconsole:jdk自帶,功能簡單,可是能夠在系統有必定負荷的的狀況下使用。對垃圾回收算法有很詳細的跟蹤。
Jprofile:商業軟件,須要付費。功能強大。
VisualVM:JDK自帶,功能強大,與JProfile相似。推薦。

JVM調優監控工具
jstat(性能分析)
https://www.jianshu.com/p/213710fb9e40

jstack(分析線程狀況)
https://jingyan.baidu.com/article/4f34706e3ec075e387b56df2.html
http://www.importnew.com/23601.html

jmap(堆棧dump)
https://www.cnblogs.com/kongzhongqijing/articles/3621163.html

jinfo(jvm信息狀況)
https://www.jianshu.com/p/ece32dacce64

jps(jvm進程狀況)
https://www.jianshu.com/p/d39b2e208e72

JVM調優工具轉載至一些優質的博客,能夠詳細學習參考

文檔整理於JVM視頻學習課程,感謝提供教育視頻的IT人才們,整理分享還未接觸到這層知識的人們,也收藏我的學習。

相關文章
相關標籤/搜索