JVM 一套卷,助你快速掌握優化法則 頂 原

一:虛擬機內存圖解

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

虛擬機管理內存數據區域劃分如下圖:

數據區域分類:

方法區: (Method Area)

虛擬機棧 : (VM Stack)

本地方法棧 : (Native Method Stack)

堆: (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 異常。

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

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

1、 句柄方式 (訪問)

2、指針方式 (訪問)

優缺點:

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

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

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

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

1、參數類型

  1. 堆空間參數
  2. 棧空間參數
  3. 方法區空間參數
  4. 本機直接內存參數

2、異常類型

  1. OutOfMemory 異常
  2. StackOverflowError 異常

3、輔助參數說明

  1. -XX:+HeapDumpOnOutOfMemoryError 打印堆內存異常時打印出快照信息
  2. -XX:+HeapDumpPath 快照輸出路徑
  3. -Xmn 指定 eden 區的大小 -XX:SurvirorRation 來調整倖存區的大小
  4. -XX:PretenureSizeThreshold 設置進入老年代的閥值

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

1). 堆內存參數

  1. -Xms:堆最小值(新生代和老年代之和)
  2. -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 異常

四:垃圾收集算法

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

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

回收前狀態:

回收後狀態:

優缺點:

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

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

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

2、複製算法 (Copying)

回收前狀態:

Eden 內存空間 8

Survivor1 空間(From 空間)1

Survivor2 空間 (To 空間) 1

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

回收後狀態:

Survivor1 空間(From 空間)1

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

優缺點:

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

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

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

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

回收前狀態:

回收後狀態:

優缺點:

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

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

五:垃圾收集器

1、七種垃圾收集器

(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 纔可以正式商用)

2、1~3 用於年輕代垃圾回收:年輕代的垃圾回收稱爲 minor GC

3、4~6 用於年老代垃圾回收(當然也可以用於方法區的回收):年老代的垃圾回收稱爲 full GC

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

注意:並行與併發

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

併發:垃圾回收線程與用戶線程一起操作

4、常用五種組合

Serial/Serial Old

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

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

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

G1:最先進的收集器,但是需要 JDK1.7update14 以上

5. 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 時間

6.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 組合時,也可能會用到

7.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 服務器爲例

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

適用場合:

很多的 CPU 計算任務而用戶交互任務較少的情況不想自己去過多的關注 GC 參數,想讓虛擬機自己進行調優工作

8、調優方法

8.1 新對象預留新生代

由於 fullGC(老年代) 的成本遠比 minorGC(新生代和老年代)的成本大,所以給應用分配一個合理的新生代空間,儘量將對象分配到新生代減小 fullGC 的頻率

8.2 大對象進入老年代

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

8.3 穩定與震盪的堆大小

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

8.4 吞吐量優先

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

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

8.5 降低停頓

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

9、獲取 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  指定日誌情況的保存路徑

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

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

11、jvm 深入學習

如果想要系統深入學習 JVM 的話,我在這裏給大家推薦一個 Java 方面的中高級交流學習羣:650385180,裏面會分享一些資深架構師錄製的視頻錄像:有 Spring,MyBatis,Netty 源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM 性能優化這些成爲架構師必備的知識體系。還能領取免費的學習資源,相信對於已經工作和遇到技術瓶頸的碼友,在這個羣裏會有你需要的內容。

六:監控工具

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

1、jps

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

Jps [option] [hostid]

jps -q 只輸出 LVMID

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

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

jps -v 輸出 JVM 的啓動參數

2、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 編譯的方法

3、jinfo

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

Jinfo [option] pid

4、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

5、jstack

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

Jstack [option] vmid

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

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

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

6、jconsole

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

7、jprofile

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

注:文章有點長,大家覺得作者總結的還可以,大家可以點擊左邊的二維碼進行關注。《Java技術zhai》公衆號聊的不僅僅是Java技術知識,還有面試等乾貨,後期還有大量架構乾貨。大家一起關注吧!關注技術zhai,你會了解的更多..............

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章