JVM原理探究及調優方法論

1 此文目的

本文不許備從盤古開天地開始講述JVM的種種,相關的文章網上太多了,大多也無非轉來轉去,連圖都差很少。筆者只整理個提綱挈領的學習路線指南,並對本身學習過程當中遇到的坑和容易混淆和忽視的地方做個總結。html

2 JVM內存模型

2.1 內存模型

內存區域劃分有多個維度,相同區域在不一樣維度的名稱並不同。以下圖所示java

能夠看到,survivor區被劃分爲了survivor0和survivor1兩個區域,可是在講MinorGC的原理時,咱們又會說survvior to和survivor from兩個區域。事實上,survivor0和survivor1是物理維度的劃分,而survivor to和survivor from是邏輯維度的劃分,在MinorGC的過程當中,survivor0和survivor1交替擔當to區和from區。 來仔細解釋一下MinorGC的過程: 在GC開始的時候,對象只會存在於Eden區和名爲「From」的Survivor區,Survivor區「To」是空的。緊接着進行GC,Eden區中全部存活的對象都會被複制到「To」,而在「From」區中,仍存活的對象會根據他們的年齡值來決定去向。年齡達到必定值(年齡閾值,能夠經過-XX:MaxTenuringThreshold來設置)的對象會被移動到年老代中,沒有達到閾值的對象會被複制到「To」區域。通過此次GC後,Eden區和From區已經被清空。這個時候,「From」和「To」會交換他們的角色,也就是新的「To」就是上次GC前的「From」,新的「From」就是上次GC前的「To」。無論怎樣,都會保證名爲To的Survivor區域是空的。Minor GC會一直重複這樣的過程,直到「To」區被填滿,「To」區被填滿以後,會將全部對象移動到年老代中。大體以下圖所示:

2.2 方法區和永久代

這兩個概念,不少時候都被當作是同一個概念。實際上,「方法區」是java虛擬機規範中對存放類信息,字段,方法,常量,靜態變量,接口和常量池的內存區域的定義,而「永久代」則是HotSpot VM在1.8版本之前對於方法區的具體實現。因爲java虛擬機規範並無對方法區的具體實現做限制,因此HotSpot VM和JRocket VM對於方法區的實現都是不同的,JRocket中就沒有永久代的概念。而在1.8及1.8之後的版本中,HotSpot VM用"元空間"--metaspace來代替永久代,實現方法區。 這個變化帶來的就是VM參數的變化,全部的PermGen都被替換成了MetaSpace。而且metaSpace再也不使用堆內存,而是使用系統內存。可是該發生的OOM同樣會發生。緣由也基本都是加載到內存中的 class 數量太多或者體積太大。算法

3.GC

3.1 GC算法

GC算法和GC收集器也是兩個維度的概念。 GC算法包括清除算法(也叫標記清除算法),複製算法,標記-整理算法。 不一樣垃圾收集器針對不一樣的內存區域,採用不一樣的GC算法。 具體介紹,網上相關資料不少,能夠參考這篇文章:blog.csdn.net/xiaoping091…瀏覽器

3.2 垃圾收集器

垃圾收集器經歷了從串行收集器到並行收集器,再到併發收集器的進化過程。這三者的區別以下圖所示tomcat

串行和並行的區別比較容易理解,而CMS垃圾收集器的原理要注意的是,雖然它是併發收集器,但它的GC線程並非完徹底全地與應用的進程併發進行,它只是經過用兩次短暫停來代替並行GC的一次長暫停,以期達到減小應用線程暫停的目的,詳見 CMS垃圾回收機制

不一樣版本默認使用的垃圾收集器以及支持開發者定製的垃圾收集器都是不同的 jdk1.7 默認垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代) jdk1.8 默認垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代) jdk1.9 默認垃圾收集器G1 與此同時,經過設置JVM參數也能夠本身選擇垃圾收集器。如要開啓G1垃圾回收器,能夠用-XX:+UseG1GC,支持G1垃圾回收器的JDK最低版本爲JDK 7u4。在用戶本身選擇垃圾收集器的時候,要注意JDK版本的問題。 筆者用表格的形式列出了新生代和老年代的GC收集器的常見搭配方案:併發

3.3 Full GC觸發條件

頻繁FullGC致使的stop the world的現象,會大大影響系統的穩定性。儘管一代又一代的垃圾收集器的優化,使得stop the world的時間愈來愈短,可是在大型應用中,仍是避之不及。 出發FullGC的狀況有如下幾種:eclipse

  1. System.gc()方法的調用
  2. 老年代不足
  3. 方法區不足
  4. concurrent mode failure concurrent mode failure是在執行CMS GC的過程當中同時有對象要放入老年代,而此時老年代空間不足形成的(有時候「空間 不足」是CMS GC時當前的浮動垃圾過多致使暫時性的空間不足觸發Full GC)。
  5. promotion failed minor gc時年輕代的存活區空間不足而晉升老年代,老年代又空間不足而觸發full gc
  6. 統計獲得的Minor GC晉升到舊生代的平均大小大於老年代的剩餘空間 當準備要觸發一次young GC時,若是發現統計數聽說以前young GC的平均晉升大小比目前old gen剩餘的空間大,則不會觸發young GC而是轉爲觸發full GC(由於HotSpot VM的GC裏,除了CMS的concurrent collection以外,其它能收集old gen的GC都會同時收集整個GC堆,包括young gen,因此不須要事先觸發一次單獨的young GC)。

3.3.1 OOM的類型

一般狀況下,JVM的GC機制能保證應用的正常運行,致使系統頻繁FullGC的緣由百分之九十都是內存溢出(OOM)。OOM分爲如下幾類:jvm

  1. Java.lang.OutOfMemeoryError:Java heap space 堆空間的內存溢出,可能的緣由是某個可達性分析認爲不能被回收的對象隨着時間推移變得愈來愈大,例如某個static類型的map對象,被不停地塞入鍵值對,也多是大循環或者死循環不斷建立對象,而對象分配內存的速度超過了GC清理內存的速度。
  2. Java.lang.OutOfMemeoryError:GC overhead limit exceeded 這種OOM異常是Hotspot VM 1.6定義的一個策略,經過統計GC時間來預測是否要OOM了,提早拋出異常,防止OOM發生。Sun 官方對此的定義是:「並行/併發回收器在GC回收時間過長時會拋出OutOfMemroyError。過長的定義是,超過98%的時間用來作GC而且回收了不到2%的堆內存。用來避免內存太小形成應用不能正常工做。」 那麼爲何會出現這種GC效率低下的現象呢?一般是由於老年代內存佔有過多致使的頻繁GC,這種狀況下,能夠增長-verbose:gc -XX:+PrintGCDetails來分析具體緣由,也能夠加-XX:+HeapDumpOnOutOfMemoryError,這樣OOM時會自動作Heap Dump,第二種方法適用於全部OOM異常的排查。
  3. Java.lang.OutOfMemoryError: PermGen space(JAVA8引入了Metaspace區域)方法區內存溢出,一般是由於加載的類過多,能夠先排除程序問題致使的重複類加載,或者加大方法區內存的分配
  4. Java.lang.OutOfMemoryError: unable to create new native thread 產生這種異常的緣由是因爲系統在不停地建立大量的線程,且不進行釋放。

4. JVM調優

4.1 調優參數

正確設置JVM參數,能夠儘量多地避免系統資源浪費,儘量詳細地掌握系統運行狀況,而且對可能出現的問題防患於未然。ide

Xms:堆初始空間工具

Xmx:堆最大空間

Xmn:年輕代大小

XX:MaxNewSize 新生代最大空間 建議設置爲整個堆的1/3到1/4

XX:NewSize

XX:MaxTenuringThreshold survivor中到老年代中的年齡閾值

Xss:每一個線程的棧大小

java -XX:+PrintCommandLineFlags -version 獲得JDK建議的內存分配大小

tomcat設置catalina.sh:

export JAVA_OPTS="-server –Xms1024m -Xmx1024m -XX:+UseParallelOldGC -verbose:gc -Xloggc:../logs/gc.log

-XX:+PrintGCDetails -XX:+PrintGCTimeStamps"

-XX:+PrintCommandLineFlagsjvm參數可查看默認設置收集器類型

-XX:+PrintGCDetails亦可經過打印的GC日誌的新生代、老年代名稱判斷

4.2 JVM監控

4.3 JVM異常排查

  1. 保存dump 當使用監控軟件或者命令查看發現JVM異常時,應第一時間保存下dump現場。 命令是jmap -dump:format=b,file=文件名[pid] pid是服務進程

若是是使用jvisualvm就更方便了,直接點擊如圖所示的按鈕便可:

2. 分析dump eclipse有一款插件叫作Memory Analyzer(MAT),可是目前idea並無這款插件 此外,jhat是sun 1.6及以上版本中自帶的一個用於分析JVM 堆DUMP 文件的工具,基於此工具可分析JVM HEAP 中對象的內存佔用狀況 jhat -J-Xmx1024M [file] 執行後等待console 中輸入start HTTP server on port 7000 便可使用瀏覽器訪問 IP:7000 能夠特別關心下圖標出的這個選項 這對於排查堆內存溢出很是有效

4.4 實戰例子

因爲實際工做中,能接觸到JVM機會的機會並很少,因此筆者整理了一些經典實例

Metaspace溢出排查過程

分享一次 Java 內存泄漏的排查

一次生產的 JVM 優化案例

JVM成長之路,記錄一次內存溢出致使頻繁FGC的問題排查及解決

很是詳細的jvm調優實例,性能瓶頸定位

相關文章
相關標籤/搜索