JVM 調優主要是調整GC以及一些執行參數:java
目標:web
堆不要太大,否則單次GC的時間過長致使服務器沒法響應的問題算法
壓力測試的時候TPS平穩安全
儘可能避免full GC服務器
檢查是否用了並行的垃圾回收器多線程
參數:併發
-server執行,開啓優化oracle
採用並行gc collector, -XX:+UseParallelGC +XX:+UseParallelOldGC +XX:+UseConcMarkSweepGCjvm
-Xmx不要太大,否則單次gc的過程可能太長,大內存機器能夠採用多個實例的方式性能
-Xms不要過小,否則jvm須要屢次調整堆的大小,增長gc次數,影響了啓動性能。
同理-XX:MaxPermSize
-XX:NewRatio新生代與老年代的比例要合適,這個須要看應用類型,通常新生代的對象很快就會被GC掉了。
線程池的大小要合適,線程上下文切換也是很耗資源的。
不要去調整一些你不徹底瞭解的參數。
還有-Xverify:none能夠加快啓動速度,但字節碼問題查錯很麻煩。
字節碼調優應該避免吧,容易觸發一些jvm自己的bug,這些參數缺乏實際場景的測試。
CMS垃圾收集器的相關參數,請讀取相關資料。貌似CMS不會去移動對象的去使得空間更加緊湊。
G1垃圾收集器的工做原理,G1能夠Compact Heap Region以減小內存碎片問題。
相關連接:一個Oracle jvm部門裏面的員工的blog: https://blogs.oracle.com/jonthecollector/ 裏面的文章頗有價值。
一些命令:
// 監控gc的狀況,每隔2秒輸出一次gc的信息 jstat -gcutil pid 2000 // 每列的語言以下 S0(Survivor0) S1(Survivor1) E(Eden) O(Old) P(Perment) YGC (Young GC Count) YGCT(Young GC Time) FGC(Full GC Count) FGCT(Full GC Time)
一、爲何要使用自動內存管理
爲了消除手動內存管理帶來的複雜性以及內存泄漏,dangling引用,內存碎片等一系列問題
二、動態內存分配是一個比較複雜的工做,由於它要使用內存的分配及釋放足夠快,同時還要考慮內存碎片問題。
三、垃圾回收器必須儘可能少使得程序暫停,同時也須要在 回收耗時、空間大小,回收次數這幾個方面取得一個平衡,另外還須要控制內存碎片。
四、垃圾回收器必須是可擴展,它不該該成爲程序性能的瓶頸,它應該能夠在多線程/多CPU的環境並行得執行。
五、垃圾回收器最好可以併發地進行內存回收工做,併發狀況下,堆被劃分紅幾個區域,這些區域會被併發地進行回收,由此減小了回收工做所引發的程序的暫停。
六、Compacting vs Non-Compacting vs Copying
七、垃圾回收器性能評判的幾個指標:GC時間佔程序運行時間的比重,非GC時間佔比,GC引發的暫停時間,回收的頻率,檢測到垃圾對象的速度,回收器工做消耗的內存大小。
八、內存分代回收,比較流行分爲young generation(新生代)及old generation(老年代),大多數分配的對象不會存活得過久,一般只有一小部分對象能夠進化到老年代。所以新生代的區域回收得比較頻繁,而老年代空間經過佔用比較多的空間,所以各個區域會使用不一樣的算法進行垃圾回收。好比-XX:NewRatio=4表示 young generation : old generation = 1 : 4。在HotSpot Virutial Machine中,內存被分爲三個區域,young generation, old generation和permenent generation,大部分對象都是在新生代進行分配的,有些對象會直接在老年代進行分配。新生代的空間由Eden生兩個survivor區域來組成,一個survivor用來存放至少存活超過一次young generation GC的對象,而另外一個survivor空間是空的,直接下一次回收的時候會被使用。一個suvivor與Eden的大小比值能夠用-XX:SurvivorRatio=n來表示(1/n=survivor : Eden)
當old generation因塞滿而沒法存放young generation升級上來的對象時,將觸發full GC,這時大部分的收集器會用老年代的算法去GC整個Heap(除了CMS Collector外, CMS Collector的老年代算法沒法回收新生代區域)。
快速分配,在大片鏈接的內存塊中進行分配內存的效率是很高的,能夠利用bump-the-pointer技術來進行分配,分配器會記住下一次分配的起點。對於多線程的程序來講,內存分配操做須要是線程安全的,若是使用全局鎖的話這會下降性能並形成性能瓶頸,相應的HotSpot採用一種叫作Thread-Local Allocation Buffers的技術(線程本身的內存分配緩衝區),使得減小獲取全局鎖的操做,一般TLAB佔用大概1%的Edgen的大小。結合TLAB和bump-the-pointer技術,一般分配一個對象空間只須要10條本地指令。
當使用Serial Collector時候,young generation和old generation的回收工做都是使用單個CPU線性執行的,回收過程當中將stop-the-world。下圖展現了Serial Collector對young generation進行回收時過程,Eden區域存活的對象被複制到那個空的Survivor塊(圖中用to標記),若是對象太大超出了Survivor的大小,那麼它將直接被copy到old generation區域,而非空的survivor(圖中用from標記)中仍然年輕的對象也被複制到空的survivor中(圖中用to標記),而相對比較「老」的對象則被複制到old generation區域中。
注意:若是"To" survivor被塞滿了話,Eden和"From"Survivor區域尚未被copy的對象將直接被複制到old generation中(不管它們存活多少了多少代)。
在完成了對young generation的回收以後,young generation中Eden區域和"From" survivor都被被清空,只有"To" survivor中有存活的對象。這時,"From"和"To" Survivor將對換,以下圖所示:
注意,存在old generation引用young generation對象的狀況,爲了不進行young gc的時候掃描old generation,老年代對new generation的引用被記錄在一個叫作card table的cache中。
當使用Serial Collector對old generation和permenent generation進行回收的時候,它將使用一種mark-sweep-compact的回收算法:
Mark階段:Collector檢測那些對象仍然存活。
Sweep階段:Collector掃描整個old generation或者permenent generation,
Compact階段:Collector將存活的對象「滑動」到old generation的首部,全部連續的空間放在old generation的尾部,這樣方便利用bump-the-pointer來實現快速地分配對象。以下圖所示:
大部分以Client-Style(java -client)運行的程序都使用這種收集器,這類程序對回收引種的程序暫停時候不敏感。在如今的硬件條件下,Serial Collector可以管理64MB大小的堆空間(如今應該能夠256MB了吧)。
在JavaSE5中,運行非server-class的機器上默認使用Serial Collector,但用戶可使用-XX:+UseSerialGC命令參數來指定使用Serial Collector。
現在,不少程序運行在擁有大內存多CPU的機器上面,Parallel Collector就是爲了在垃圾回收過程當中充分利用CPU資源而開發的。
Parallel Collector使用一種相似於Serial Collector對young generation回收的算法的並行版本,回收時它仍然會stop-the-world,但在回收的過程當中它並行地使用多個cpu並行地執行,由些來減小垃圾回收所佔用的時候並提高程序運行時間的佔比。
Parallel Collector使用了與Serial Collector一樣的mark-sweep-compact的回收算法。
何時使用Parallel Collector
運行在多cpu以及對中止時間不敏感的程序能夠從使用parallel collector中受益,不頻繁,耗時較長的針對old generation區域回收的仍然會發生, 批量處理,計費,工資以及科學計算這類程序比較適合使用Parallel Collector。
JavaSE5中,運行server-class的機器上默認使用Parallel Collector,用戶可使用-XX:UseParallelGC命令參數來顯示指定使用Parallel Collector
Parallel Compacting Collector在JavaSE5.0 update 6被引入,與Parallel Collector不一樣的是,它使用了一種新的算法來對old generation進行回收,最終Parallel Compacting Collector將取代Parallel Collector
回收young generation時,Parallel Compacting Collector使用了與Parallel Collector一樣的算法。
回收old generation和permenent generation時,Parallel Compacting Collector仍然會stop-the-world,但在整理存活對象的時候大部分是並行的。Parallel Compacting Collector使用三個階段進行,
1、young/old/permenent generation區域被劃分紅幾個固定大小的區域
2、marking階段,程序中仍然能夠引用到的存活的對象被劃分給幾個garbage collection threads中,而後mark工做是併發執行地,當一個對象被標記爲存活的時候,它所在的regioin的大小將會被更新。
3、Summary階段,經過前幾回的收集,generation空間的首部會存活的對象會比較密集,經過compacting能回收的空間比較少,所以不值得在上面進行compacting,因此summary階段所作的第一件事就是檢驗regions的密度,從最左邊開始,直到碰到一個密度比較小,值得花時間去compacting的region,而後從這個region開始,compacting右邊的region。summary階段計算並存放被compacting region的新的首地址(這個階段並無真正地去Compacting)。注意:summary段是單線程執行的,儘管它能夠實現爲併發執行。但事實代表併發執行的
4、compacting階段,在這個階段中,利用上一階計算出來的Compacting信息,各個線程能夠獨立地往region移動對象。Compacting完成以後,堆空間的後部將釋放出一片連續的空間。
在Parallel Collector的基礎上,Parallel Compacting Collector進一步減小了因爲回收old generation所消耗的時候,進一步知足對垃圾回收引發的暫停時間 比較敏感的程序。但須要注意的是,Parallel Compacting Collector 能夠不適合那些運行在大型機/刀片機的程序,這種機器上是不容許單獨一個程序佔用幾個cpu過長時間,在這種環境下能夠考慮利用參數-XX:ParallelGCThread=n,或者選擇另外的垃圾回收器。
須要使用-XX:+UseParallelOldGC來顯式指定使用Parallel Compacting Collector,這個參數的名字有點奇怪,這裏的"Old"是指old generation。
對於不少應用來講,應用運行時間佔比(throughput)沒有響應時間這麼重要,young generation區域的回收一般不會形成太長時間的暫停。可是old generation的回收,儘管不是很頻繁,但一般會強制應用暫停比較長的時間。爲了解決這個問題,HotSpot JVM 引入了一個叫作concurrent mark-sweep (CMS)的收集器,也叫作low-latency collector(低延遲迴收器)
CMS與Parallel Collector使用一樣的方式回收young generation
CMS在回收old generation大多數時候都是在程序運行時併發地執行,在開始一次完整的回收以前,CMS須要暫停一下程序(stop-the-world),這個過程叫作初始標記(Initial Mark),這個過程當中查找程序代碼中能夠直接引用到的對象(一般是線程棧上的對象引用),而後在併發標記階段,CMS去標記全部可達的對象,由於這個工做是併發進行的,應用同時也在更新一些字段的引用,因此在併發標記以後須要來一個stop-the-world,將新產生的對象標記完整,這個過程被稱爲remark,remark比initial mark更加耗時,因此通常使用多個線程併發地執行來提交效率。
在remark結束以後,全部的可達的對象都被標記了,而後接下來的併發掃描階段將回收垃圾,下圖闡述了CMS Collector與Serial mark-sweep-compact Collector之間的差異:
由於有一些工做,好比在remark階段從新遍歷對象,增長了collector的工做量,因此CMS回收時佔用的CPU和內存資源也更多,但它減小了應用的中止時間。
須要注意的是,CMS collector是惟一一個不會去compact(整理)內存的收集器,以下圖所示,這節省了一些時間,但由於這些空間並不鏈接,bump-the-pointer也不奏效了,所以它須要使用一個free列表去記錄可以使用的空間,而後在分配時去查找這個list。所以分配空間的操做效率相對要低一些,同時,這也會形成回收young generation的一些負擔,所以回收young generation時須要從young generation裏面複製一些存活的對象到old generation中,內存碎片的風險也增長了。
另外,與其它收集器不一樣的是,當old generation滿了以後,它並不會發起一個old generation的回收,相反,它會嘗試在尚未滿的時候發起一次回收(由於在concurrent mark階段程序是併發運行地),以便可以用完以前可以完成回收,不然它將轉爲使用與parallel collector 和serial collector同樣stop-the-world方法去回收這部分空間。爲了不這一點,CMS Collector定時記錄了一些垃圾回收的數據,好比回收的速率,而後在恰當的時候觸發回收操做,避免在用完的時候再進行回收。另外,當old generation佔用超過必定程度以後,CMS Collector也會去發起一次回收操做,能夠用-XX:CMSInitiatingOccupanyFraction=n,n是old generation大小的佔比,默認是68。
總之,CMS可以減小大部分程序因爲回收工做而被暫停的時間 ,但結果的代價是:回收young generation變慢了,程序運行時間佔比降低,以及更大的堆空間消耗。
CMS Collector能夠運行在增量回收的模式下,這種模式下,young generation回收過程的時候被分爲幾個小塊的時間段。以減小stop-the-world時間。
CMS Collector適合那些對暫時時間比較敏感,容許GC操做併發地使用CPU,並且有大量存活對象的應用,好比web server。
必須使用-XX:+UseConcMarkSweepGC來顯示指定使用CMS Collector,若是須要運行在增量模式下,必須使用-XX:+CMSIncrementalMode參數。
在-server模式下,jvm的默認初始heap大小是1/64的物理內存(最多1GB),默認最大堆大小是1/4物理內存大小(最大1GB)
-client模式下,默認4MB初始heap大小及64MB的最大堆大小。固然這些均可以經過命令參數進行覆蓋。
另外還可使用-XX:MaxGCPauseMillis=n來指定GC形成的最大停頓時間,這個時間不必定能完成,不能完成的話,Collector會在堆未佔滿的狀況觸發回收操做。
另外也可使用-XX:GCTimeRatio=n來設置GC時間佔比(GC:程序運行時間),好比GCTimeRatio=4的狀況下,GC時間將最大佔用20%的時間。和-XX:MaxGCPauseMillis同樣,若是不能知足要求,Collector將在geneartion佔滿以前觸發回收操做。