認真看完這篇文章,JVM將再也不是你的短板

前言

 

想起當年仍是個菜鳥的時候,看了許多帖子,裏面的Java大神們都在說:JVM調優是JavaIT人進階所必備的知識。java

因而就買了書和教程去看,可是發現對於調優這個話題看書效率奇低,有些內容反覆看了又看,回想起來腦子裏卻不剩什麼。linux

爲了不你們出現相似的狀況,特意編輯了一篇JVM相關文章供你們參考學習,但願能幫助到想要進步的你。算法

 

一:虛擬機內存圖解數組

 

JAVA程序運行與虛擬機之上,運行時須要內存空間。虛擬機執行JAVA程序的過程當中會把它管理的內存劃分爲不一樣的數據區域方便管理。tomcat

 

虛擬機管理內存數據區域劃分以下圖:服務器

 

 

數據區域分類:數據結構

 

方法區:(Method Area)多線程

虛擬機棧:(VM Stack)併發

本地方法棧 :(Native Method Stack)jvm

堆:(Heap)

程序計數器 :(Program Counter Register)

直接內存:(Direct Memory)

 

說明:

 

1. 程序計數器

 

行號指示器,字節碼指令的分支、循環、跳轉、異常處理、線程恢復(CPU切換),每條線程都須要一個獨立的計數器,線程私有內存互不影響,該區域不會發生內存溢出異常。

 

2. 虛擬機棧

 

是線程私有的,聲明週期與線程相同,虛擬機棧是Java方法執行的內存模型,每一個方法被執行時都會建立一個棧幀,即方法運行期間的基礎數據結構,棧幀用於存儲:局部變量表、操做數棧、動態連接、方法出口等,每一個方法執行中都對應虛擬機棧幀從入棧處處棧的過程。

 

是一種數據結構,是虛擬機中的局部變量表,對應物理層之上的程序數據模型。

 

局部變量表,是一種程序運行數據模型,存放了編譯期可知的各類數據類型例如:

Boolean、byte、char、short、int、float、long、double、對象引用類型(對象內存地址變量,指針或句柄),程序運行時,根據局部變量表分配棧幀空間大小,在運行中,大小是不變的異常類型:stackOverFlowError 線程請求棧深度大於虛擬機容許深度 OutOfMemory 內存空間耗盡沒法進行擴展。

 

3. 本地方法棧

 

與虛擬機棧相似,虛擬機棧爲Java程序服務,本地方法棧支持虛擬機的運行服務,具體實現由虛擬機廠商決定,也會拋出 stackOverFlowError、OutOfMemory異常。

 

4. 堆

 

是虛擬機管理內存中最大的一部分,被全部線程共享,用於存放對象實例(對象、數組),物理上不連續的內存空間,因爲GC收集器,分代收集,因此劃分爲:新生代 Eden、From SurVivor空間、To SurVivor空間,allot buffer(分配空間),可能會劃分出多個線程私有的緩衝區,老年代。

 

5. 方法區

 

與堆同樣屬於線程共享的內存區域,用於存儲虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼(動態加載OSGI)等數據。理論上屬於java虛擬機的一部分,爲了區分開來叫作 Non-Heap非堆。

 

這個區域能夠選擇不進行垃圾回收,該區域回收目的主要是常量池的回收,及類型的卸載class,內存區不足時會拋出OutOfMemory異常

 

運行時常量池:

 

方法區的一部分,Class的版本、字段、接口、方法等,及編譯期生成的各類字面量、符號引用,編譯類加載後存放在該區域。會拋出OutOfMemory異常。

 

6. 直接內存

 

直接內存不屬於虛擬內存區域,是一種基於通道與緩衝區的IO方式,可使用本地函數直接分配堆外內存,在堆中存儲引用的外部內存地址,經過引用完成對直接引用內存的操做,1.4以後提供的NIO顯著提升效率,避免了堆內存與Native內存的來回複製操做,不受虛擬機內存控制,會拋出OUtOfMemory異常。

 

二:對象訪問內部實現過程

 

對象訪問 涉及到對象的地址變動狀態變動,內存地址移動,變量、接口、實現類、方法、父類型等。

 

一、 句柄方式 (訪問)

 

 

二、指針方式 (訪問)

 

 

優缺點:

 

句柄訪問方式:reference中存儲的是穩定的地址,對象變動時只會改變句柄實例數據指針,引用自己不須要修改

 

指針訪問方式:優勢速度快,節省了指針定位時間開銷

 

三:內存區域控制參數及對應溢出異常

 

開發過程當中,或程序運行過程當中每次遇到OutOfMemory異常或GC異常或StackOverflowError異常咱們都是一堆參數亂配,都把值調大,只是大致知道是跟jvm內存分配有關,具體應該怎麼調,對應的異常應該調整那些參數,或者換句話說,jvm內存分配區域中都分別對應那些參數大多數狀況下都是不知道的,只是把相關的參數跳上去,預期結果都是應該起做用,到底能不能起做用,本身內心也沒底。下面就來講一下jvm堆、棧、方法區等內存區域對應的參數,及每一個區域可能拋出的異常類型,發生異常的場景分析。

 

一、參數類型

 

  1. 堆空間參數

  2. 棧空間參數

  3. 方法區空間參數

  4. 本機直接內存參數

 

二、異常類型

 

1.OutOfMemory異常

2.StackOverflowError異常

 

三、輔助參數說明

 

  1. -XX:+HeapDumpOnOutOfMemoryError 打印堆內存異常時打印出快照信息

  2. -XX:+HeapDumpPath 快照輸出路徑

  3. -Xmn指定eden區的大小

  4. -XX:SurvirorRation來調整倖存區的大小

  5. -XX:PretenureSizeThreshold設置進入老年代的閥值

 

四、參數說明、對應場景的異常

 

1.堆內存參數

 

-Xms:堆最小值(新生代和老年代之和)

-Xmx:堆最大值(新生代和老年代之和)

 

當最小值=最大值時,這時堆內存是不可擴展的。

 

例:-Xms80M -Xmx80M

 

一般將-Xmx和-Xms設置爲同樣的大小來減小gc的次數,堆內存不足時拋出OutOfMemoryError異常。

 

2.棧內存參數

 

-Xss

 

例:-Xss128k

 

單線程下不管棧幀太大仍是棧容量過小,及引用深度超過虛擬機容許深度都會拋出StackOverflowError每一個方法壓入棧的幀大小是不一致的。多線程下當每一個線程分配棧幀太大內存不可以擴展時拋出OutOfMemoryError異常線程棧幀越大,可建立的線程越少。

 

3.方法區參數

 

-XX:PermSize方法區內存最小值

-XX:MaxPermSize 方法區內存最大值

 

各個線程共享的內存區域,主要用來存儲類的元數據、常量、靜態變量、即時編譯器編譯後的代碼等數據

 

例:-XX:PermSize=20M -XX:MaxPermSize=20M

 

異常類型 OutOfMemoryError :

 

緣由:常量過多,或代理反射等使用頻繁

 

4.本機直接內存參數

 

-XX:MaxDirectMemorySize

 

例:-XX:MaxDirectMemorySize=10M

 

不足時拋出OutOfMemory異常

 

四:垃圾收集算法

 

經典的垃圾回收算法如下幾種

 

一、標記--清除算法(Mark-Sweep)

 

回收前狀態:

 

 

回收後狀態:

 

 

優缺點:

 

算法執行分爲兩個階段標記與清除,全部的回收算法,基本都

基於標記回收算法作了深度優化

 

缺點:效率問題,內存空間碎片(不連續的空間)

 

二、複製算法(Copying)

 

回收前狀態:

 

Eden內存空間 8

 

 

Survivor1空間(From空間)1

 

 

Survivor2空間(To空間) 1

 

 

Eden內存空間與Survivor空間 8:1

 

 

回收後狀態:

 

 

Survivor1空間(From空間)1

 

 

Eden內存空間與Survivor空間 8:1

 

 

優缺點:

 

比較標記清除算法,避免了回收形成的內存碎片問題

 

缺點:以局部的內存空間犧牲爲代價,不過空間的浪費比較小,默認8:1的比例1是浪費的。

 

複製也有必定的效率與空間成本

 

三、標記整理算法(Mark-Compact)

 

回收前狀態:

 

 

回收後狀態:

 

 

優缺點:

 

避免了,空間的浪費,與內存碎片問題。

 

缺點:整理時複製有效率成本。

 

五:垃圾收集器

 

一、七種垃圾收集器

 

(1) Serial(串行GC)-XX:+UseSerialGC

(2) ParNew(並行GC)-XX:+UseParNewGC

(3) Parallel Scavenge(並行回收GC)

(4) Serial Old(MSC)(串行GC)-XX:+UseSerialGC

(5) CMS(併發GC)-XX:+UseConcMarkSweepGC

(6) Parallel Old(並行GC)-XX:+UseParallelOldGC

(7) G1(JDK1.7update14才能夠正式商用)

 

 

二、1~3用於年輕代垃圾回收

 

年輕代的垃圾回收稱爲minor GC

 

三、4~6用於年老代垃圾回收(固然也能夠用於方法區的回收)

 

年老代的垃圾回收稱爲full GC

 

G1獨立完成"分代垃圾回收"

 

注意:並行與併發

 

並行:多條垃圾回收線程同時操做

 

併發:垃圾回收線程與用戶線程一塊兒操做

 

四、經常使用五種組合

 

Serial/Serial Old

 

ParNew/Serial Old:與上邊相比,只是比年輕代多了多線程垃圾回收而已

ParNew/CMS:當下比較高效的組合

Parallel Scavenge/Parallel Old:自動管理的組合

G1:最早進的收集器,可是須要JDK1.7update14以上

 

五、Serial/Serial Old

 

年輕代Serial收集器採用單個GC線程實現"複製"算法(包括掃描、複製)

年老代Serial Old收集器採用單個GC線程實現"標記-整理"算法

Serial與Serial Old都會暫停全部用戶線程(即STW)

 

說明:

 

STW(stop the world):編譯代碼時爲每個方法注入safepoint(方法中循環結束的點、方法執行結束的點),在暫停應用時,須要等待全部的用戶線程進入safepoint,以後暫停全部線程,而後進行垃圾回收。

 

適用場合:

 

CPU核數<2,物理內存<2G的機器(簡單來說,單CPU,新生代空間較小且對STW時間要求不高的狀況下使用)

 

-XX:UseSerialGC:強制使用該GC組合

-XX:PrintGCApplicationStoppedTime:查看STW時間

 

六、ParNew/Serial Old:

 

ParNew除了採用多GC線程來實現複製算法之外,其餘都與Serial同樣,可是此組合中的Serial Old又是一個單GC線程,因此該組合是一個比較尷尬的組合,在單CPU狀況下沒有Serial/Serial Old速度快(由於ParNew多線程須要切換),在多CPU狀況下又沒有以後的三種組合快(由於Serial Old是單GC線程),因此使用其實很少。

 

-XX:ParallelGCThreads:指定ParNew GC線程的數量,默認與CPU核數相同,該參數在於CMS GC組合時,也可能會用到

 

七、Parallel Scavenge/Parallel Old:

 

特色:

 

年輕代Parallel Scavenge收集器採用多個GC線程實現"複製"算法(包括掃描、複製)年老代Parallel Old收集器採用多個GC線程實現"標記-整理"算ParallelScavenge與Parallel Old都會暫停全部用戶線程(即STW)

 

說明:

 

吞吐量:CPU運行代碼時間/(CPU運行代碼時間+GC時間)CMS主要注重STW的縮短(該時間越短,用戶體驗越好,因此主要用於處理不少的交互任務的狀況)Parallel Scavenge/Parallel Old主要注重吞吐量(吞吐量越大,說明CPU利用率越高,因此主要用於處理不少的CPU計算任務而用戶交互任務較少的狀況)

 

參數設置:

 

-XX:+UseParallelOldGC:使用該GC組合

-XX:GCTimeRatio:直接設置吞吐量大小,假設設爲19,則容許的最大GC時間佔總時間的1/(1+19),默認值爲99,即1/(1+99)

-XX:MaxGCPauseMillis:最大GC停頓時間,該參數並不是越小越好

-XX:+UseAdaptiveSizePolicy:開啓該參數,-Xmn/-XX:SurvivorRatio/-XX:PretenureSizeThreshold這些參數就不起做用了,虛擬機會自動收集監控信息,動態調整這些參數以提供最合適的的停頓時間或者最大的吞吐量(GC自適應調節策略),而咱們須要設置的就是-Xmx,-XX:+UseParallelOldGC或-XX:GCTimeRatio兩個參數就好(固然-Xms也指定上與-Xmx相同就好)

 

注意:

 

-XX:GCTimeRatio和-XX:MaxGCPauseMillis設置一個就好

不開啓-XX:+UseAdaptiveSizePolicy,-Xmn/-XX:SurvivorRatio/-XX:PretenureSizeThreshold這些參數依舊能夠配置,以resin服務器爲例

 

<jvm-arg>-Xms2048m</jvm-arg> <jvm-arg>-Xmx2048m</jvm-arg> <jvm-arg>-Xmn512m</jvm-arg> <jvm-arg>-Xss1m</jvm-arg> <jvm-arg>-XX:PermSize=256M</jvm-arg> <jvm-arg>-XX:MaxPermSize=256M</jvm-arg> <jvm-arg>-XX:SurvivorRatio=8</jvm-arg> <jvm-arg>-XX:MaxTenuringThreshold=15</jvm-arg> <jvm-arg>-XX:+UseParallelOldGC</jvm-arg> <jvm-arg>-XX:GCTimeRatio=19</jvm-arg> <jvm-arg>-XX:+PrintGCDetails</jvm-arg> <jvm-arg>-XX:+PrintGCTimeStamps</jvm-arg> View Code

 

適用場合:

 

不少的CPU計算任務而用戶交互任務較少的狀況不想本身去過多的關注GC參數,想讓虛擬機本身進行調優工做

 

八、調優方法

 

8.1 新對象預留新生代

 

因爲fullGC(老年代)的成本遠比minorGC(新生代和老年代)的成本大,因此給應用分配一個合理的新生代空間,儘可能將對象分配到新生代減少fullGC的頻率

 

8.2 大對象進入老年代

 

將大對象直接分配到老年代,保持新生代對象的結構的完整性,以提升GC效率, 以經過-XX:PretenureSizeThreshold設置進入老年代的閥值

 

8.3 穩定與震盪的堆大小

 

穩定的對大小是對垃圾回收有利的,方法將-Xms和-Xmx的大小一致

 

8.4 吞吐量優先

 

儘量減小系統執行垃圾回收的總時間,故採用並行垃圾回收器

 

-XX:+UseParallelGC或使用-XX:+UseParallelOldGC

 

8.5 下降停頓

 

使用CMS回收器,同時減小fullGC的次數

 

九、獲取gc信息的方法

 

9.1 -verbose:gc或者-XX:+PrintGC  獲取gc信息

9.2 -XX:+PrintGCDetails  獲取更加詳細的gc信息

9.3 -XX:+PrintGCTimeStamps  獲取GC的頻率和間隔

9.4 -XX:+PrintHeapAtGC  獲取堆的使用狀況

9.5 -Xloggc:D:\gc.log  指定日誌狀況的保存路徑

 

十、jvm調優實戰-tomcat啓動加速

 

在tomcat的bin/catalina.bat文件的開頭添加相關的配置

 

六:監控工具

 

監控工具:通常問題定位,性能調優都會使用到。

 

一、jps

 

Jps是參照Unix系統的取名規則命名的,而他的功能和ps的功能相似,能夠列舉正在運行的餓虛擬機進程並顯示虛擬機執行的主類以及這些進程的惟一ID(LVMID,對應本機來講和PID相同),他的用法以下:

 

Jps [option] [hostid]

jps -q 只輸出LVMID

jps -m 輸出JVM啓動時傳給主類的方法

jps -l 輸出主類的全名,若是是Jar則輸出jar的路徑

jps -v 輸出JVM的啓動參數

 

二、jstat

 

jstat主要用於監控虛擬機的各類運行狀態信息,如類的裝載、內存、垃圾回收、JIT編譯器等,在沒有GUI的服務器上,這款工具是首選的一款監控工具。其用法以下:

 

jstat [option vmid [interval [s|ms] [vount] ] ]

 

jstat 監控內容 線程好 刷新時間間隔 次數

 

jstat –gc 20445 1 20 :監視Java堆,包含eden、2個survivor區、old區和永久帶區域的容量、已用空間、GC時間合計等信息

 

jstat –gcutil 20445 1 20:監視內容與-gc相同,但輸出主要關注已使用空間佔總空間的百分比

 

jstat –class 20445 1 20:監視類的裝載、卸載數量以及類的裝載總空間和耗費時間等

 

.......-gccapcity......:監視內容與-gc相同,但輸出主要關注Java區域用到的最大和最小空間

.......-gccause........:與-gcutil輸出信息相同,額外輸出致使上次GC產生的緣由

.......-gcnew..........:監控新生代的GC狀況

.......-gcnewcapacity..:與-gcnew監控信息相同,輸出主要關注使用到的最大和最小空間

.......-gcold..........:監控老生代的GC狀況

.......-gcoldcapacity..:與-gcold監控信息相同,輸出主要關注使用到的最大和最小空間

.......-gcpermcapacity.:輸出永久帶用到的最大和最小空間

.......-compiler.......:輸出JIT編譯器編譯過的方法、耗時信息

.......-printcompilation:輸出已經被JIT編譯的方法

 

三、jinfo

 

jinfo的做用是實時查看虛擬機的各項參數信息jps –v能夠查看虛擬機在啓動時被顯式指定的參數信息,可是若是你想知道默認的一些參數信息呢?除了去查詢對應的資料之外,jinfo就顯得很重要了。jinfo的用法以下:

 

Jinfo [option] pid

 

四、jmap

 

map用於生成堆快照(heapdump)。固然咱們有不少方法能夠取到對應的dump信息,如咱們經過JVM啓動時加入啓動參數 –XX:HeapDumpOnOutOfMemoryError參數,可讓JVM在出現內存溢出錯誤的時候自動生成dump文件,亦能夠經過-XX:HeapDumpOnCtrlBreak參數,在運行時使用ctrl+break按鍵生成dump文件,固然咱們也可使用kill -3 pid的方式去恐嚇JVM生成dump文件。Jmap的做用不只僅是爲了獲取dump文件,還能夠用於查詢finalize執行隊列、Java堆和永久帶的詳細信息,如空間使用率、垃圾回收器等。其運行格式以下:

 

Jmap [option] vmip

 

監控堆棧信息主要用來定位問題的緣由,生成堆棧快照

 

.......-dump......:生成對應的dump信息,用法爲-dump:[live,]format=b,file={fileName}

.......-finalizerinfo......:顯示在F-Queue中等待的Finalizer方法的對象(只在linux下生效)

.......-heap......:顯示堆的詳細信息、垃圾回收器信息、參數配置、分代詳情等

.......-histo......:顯示堆棧中的對象的統計信息,包含類、實例數量和合計容量

.......-permstat......:以ClassLoder爲統計口徑顯示永久帶的內存狀態

.......-F......:虛擬機對-dump無響應時可以使用這個選項強制生成dump快照

 

例子:jmap -dump:format=b,file=yhj.dump 20445

 

五、jstack

 

Jstack用於JVM當前時刻的線程快照,又稱threaddump文件,它是JVM當前每一條線程正在執行的堆棧信息的集合。生成線程快照的主要目的是爲了定位線程出現長時間停頓的緣由,如線程死鎖、死循環、請求外部時長過長致使線程停頓的緣由。經過jstack咱們就能夠知道哪些進程在後臺作些什麼?在等待什麼資源等!其運行格式以下:

 

Jstack [option] vmid

 

-F 當正常輸出的請求不響應時強制輸出線程堆棧

-l 除堆棧信息外,顯示關於鎖的附加信息

-m 顯示native方法的堆棧信息

 

六、jconsole

 

在JDK的bin目錄下,監控內存,thread,堆棧等

 

七、jprofile

 

相似於jconsole,比jconsole監控信息更全面,內存,線程,包,cup 類,堆棧,等等

相關文章
相關標籤/搜索