jvm優化_day01
阿新 • • 發佈:2019-01-06
jvm的三種執行引數
jvm的優化的作用:
讓程式能更好更快的跑起來,解決生產環境下出現的bug
jvm的三種執行引數:
1. 標準引數: 不會隨著jdk版本的改變而變化
-version #jdk版本
-showversion #輸出jdk版本資訊,並繼續執行後面的命令,在除錯時非常有用
-help #檢索出所有的標準引數
-D #設定jvm的屬性引數,eg: -Dstr=hello
-server #以更多的初始堆記憶體來啟動服務,使用並行垃圾回收器,啟動速度較慢,只支援64位的作業系統
-client #以較少的初始堆記憶體來啟動服務, 使用序列垃圾回收器,啟動速度快,32位64位都支援
2. -X引數:非標準引數,在不同的jdk版本下,命令可能不同
-X #檢視當前jdk版本所有的-X引數
-Xms<size> #設定初始Java堆大小,eg: java -Xms10m TestJVM
-Xmx<size> #設定最大Java堆大小,eg: java -Xmx20m TestJvm
3. -XX引數:非標準引數,主要用於jvm調優和debug操作,-XX引數的使用有2種方式,一種是boolean型別,一種是非boolean型別:
* boolean型別
- 格式:- XX:[+-]<name> 表示啟用或禁用<name>屬性
- 如:-XX:+DisableExplicitGC 表示啟用禁止手動呼叫gc操作,也就是說呼叫System.gc()無效
* 非boolean型別
- 格式:-XX:<name>=<value> 表示<name>屬性的值為<value>
- 如:-XX:NewRatio=1 表示新生代和老年代的比值
檢視jvm執行引數:
第一種,執行java命令時打印出執行引數:
執行java命令時列印引數,需要新增-XX:+PrintFlagsFinal引數即可
eg: java -XX:+PrintFlagsFinal -version
引數有boolean型別和數字型別,值的操作符是=或:=,分別代表預設值和被修改的值。
第二種,檢視正在執行的java程序的引數:
首先使用jps -l檢視正在執行的java程式的pid,再用jinfo -flags <程序pid>檢視正在執行的java程序的引數
jdk1.7堆記憶體模型
* Young 年輕區(代)
Young區被劃分為三部分,Eden區和兩個大小嚴格相同的Survivor區,其中,Survivor區間中,某一時刻只有其中一個是被使用的,另外一個留做垃圾收集時複製物件用,在Eden區間變滿的時候, GC就會將存活的物件移到空閒的Survivor區間中,根據JVM的策略,在經過幾次垃圾收集後,任然存活於Survivor的物件將被移動到Tenured區間。
* Tenured 年老區
Tenured區主要儲存生命週期長的物件,一般是一些老的物件,當一些物件在Young複製轉移一定的次數以後,物件就會被轉移到Tenured區,一般如果系統中用了application級別的快取,快取中的物件往往會被轉移到這一區間。
* Perm 永久區
Perm代主要儲存class,method,filed物件,這部份的空間一般不會溢位,除非一次性載入了很多的類,不過在涉及到熱部署的應用伺服器的時候,有時候會遇到java.lang.OutOfMemoryError : PermGen space 的錯誤,造成這個錯誤的很大原因就有可能是每次都重新部署,但是重新部署後,類的class沒有被解除安裝掉,這樣就造成了大量的class物件儲存在了perm中,這種情況下,一般重新啟動應用伺服器可以解決問題。
* Virtual區:
最大記憶體和初始記憶體的差值,就是Virtual區。
jdk1.8堆記憶體模型
jdk1.8的記憶體模型是由2部分組成,年輕代 + 年老代。
年輕代:Eden + 2*Survivor
年老代:OldGen
在jdk1.8中變化最大的Perm區,用Metaspace(元資料空間)進行了替換。
需要特別說明的是:Metaspace所佔用的記憶體空間不是在虛擬機器內部,而是在本地記憶體空間中,這也是與1.7的永久代最大的區別所在。
在生產環境下,由於永久代記憶體經常不夠用或發生記憶體洩露,爆出異常java.lang.OutOfMemoryError: PermGen。
基於此,將永久區廢棄,而改用元空間,改為了使用本地記憶體空間。
堆記憶體的維護
1. 通過jstat命令進行檢視堆記憶體使用情況(統計分析)
jstat [-命令選項] [vmid] [間隔時間/毫秒] [查詢次數]
1.1 檢視class載入統計
jstat -class <程序pid>
* 查詢結果引數:
- Loaded:載入class的數量
- Bytes:所佔用空間大小
- Unloaded:未載入數量
- Bytes:未載入佔用空間
- Time:時間
1.2 檢視編譯統計
jstat -compiler <程序pid>
* 查詢結果引數:
- Compiled:編譯數量。
- Failed:失敗數量
- Invalid:不可用數量
- Time:時間
- FailedType:失敗型別
- FailedMethod:失敗的方法
1.3 垃圾回收統計
jstat -gc <程序pid>
* 查詢結果引數:
- S0C:第一個Survivor區的大小(KB)
- S1C:第二個Survivor區的大小(KB)
- S0U:第一個Survivor區的使用大小(KB)
- S1U:第二個Survivor區的使用大小(KB)
- EC:Eden區的大小(KB)
- EU:Eden區的使用大小(KB)
- OC:Old區大小(KB)
- OU:Old使用大小(KB)
- MC:方法區大小(KB)
- MU:方法區使用大小(KB)
- CCSC:壓縮類空間大小(KB)
- CCSU:壓縮類空間使用大小(KB)
- YGC:年輕代垃圾回收次數
- YGCT:年輕代垃圾回收消耗時間
- FGC:老年代垃圾回收次數
- FGCT:老年代垃圾回收消耗時間
- GCT:垃圾回收消耗總時間
2.通過jmap命令檢視堆記憶體的詳細資料
2.1 檢視堆記憶體的詳細使用情況
jmap -heap <程序pid>
2.2 參看堆記憶體中物件的詳細情況
jmap -histo <程序pid> #檢視所有活躍和非活躍的物件
jmap -histo:live <程序pid> #檢視所有活躍的物件
#物件說明
B byte
C char
D double
F float
I int
J long
Z boolean
[ 陣列,如[I表示int[]
[L+類名 其他物件
2.3 將堆記憶體的詳細資料dump(快照)到檔案中,最終得到的是二進位制的dat檔案
jmap -dump:format=b,file=dumpFileName <pid>
eg: jmap -dump:format=b,file=d:/test.dat 8088
3.檢視dump檔案
3.1 使用jhat檢視dump檔案
jhat -port <port> <file>
eg: jhat -port 9999 d:/test.dat
在瀏覽器輸入: http://localhost:9999 檢視dump檔案
3.2 使用MAT工具檢視dump檔案
MAT(Memory Analyzer Tool,一個基於Eclipse的記憶體分析工具,是一個快速、功能豐富的JAVA heap分析工具,官網地址:https://www.eclipse.org/mat/
3.3 使用VisualVM工具檢視dump檔案
VisualVM是jdk自帶的工具,可以檢視幾乎所有的jdk的命令,可以參看堆記憶體的使用情況,也可以檢視執行緒的使用情況,在jdk/bin目錄下,jvisualvm.exe
執行緒的維護
1. 執行緒快照命令
jstack <pid> #將正在執行的jvm的執行緒情況進行快照,並且打印出來
2.執行緒的6中狀態
在Java中執行緒的狀態一共被分成6種:
- 初始態(NEW)
- 建立一個Thread物件,但還未呼叫start()啟動執行緒時,執行緒處於初始態。
- 執行態(RUNNABLE),在Java中,執行態包括 就緒態 和 執行態。
- 就緒態
- 該狀態下的執行緒已經獲得執行所需的所有資源,只要CPU分配執行權就能執行。
- 所有就緒態的執行緒存放在就緒佇列中。
- 執行態
- 獲得CPU執行權,正在執行的執行緒。
- 由於一個CPU同一時刻只能執行一條執行緒,因此每個CPU每個時刻只有一條執行態的執行緒。
- 阻塞態(BLOCKED)
- 當一條正在執行的執行緒請求某一資源失敗時,就會進入阻塞態。
- 而在Java中,阻塞態專指請求鎖失敗時進入的狀態。
- 由一個阻塞佇列存放所有阻塞態的執行緒。
- 處於阻塞態的執行緒會不斷請求資源,一旦請求成功,就會進入就緒佇列,等待執行。
- 等待態(WAITING)
- 當前執行緒中呼叫wait、join、park函式時,當前執行緒就會進入等待態。
- 也有一個等待佇列存放所有等待態的執行緒。
- 執行緒處於等待態表示它需要等待其他執行緒的指示才能繼續執行。
- 進入等待態的執行緒會釋放CPU執行權,並釋放資源(如:鎖)
- 計時等待態(TIMED_WAITING)
- 當執行中的執行緒呼叫sleep(time)、wait、join、parkNanos、parkUntil時,就會進入該狀態;
- 它和等待態一樣,並不是因為請求不到資源,而是主動進入,並且進入後需要其他執行緒喚醒;
- 進入該狀態後釋放CPU執行權 和 佔有的資源。
- 與等待態的區別:到了超時時間後自動進入阻塞佇列,開始競爭鎖。
- 終止態(TERMINATED)
- 執行緒執行結束後的狀態。
3.產生死鎖的4個必要條件:
互斥條件: 一個資源一次只能被一個執行緒持有
請求與保持條件: 一個執行緒獲取到資源後,會繼續請求別的資源,而且會保持之前的資源
不可剝奪條件: 已持久的資源是私有財產,神聖不可侵犯
迴圈等待條件: 每個執行緒都在等待迴圈等待別的執行緒先釋放資源
4.死鎖的解決:
使用jstack命令可以快速查詢產生死鎖的程式碼
死鎖的經典程式碼
public class TestDeadLock {
private static Object obj1 = new Object();
private static Object obj2 = new Object();
public static void main(String[] args) {
new Thread(new Thread1()).start();
new Thread(new Thread2()).start();
}
private static class Thread1 implements Runnable{
@Override
public void run() {
synchronized (obj1){
System.out.println("Thread1 拿到了 obj1 的鎖!");
try {
// 停頓2秒的意義在於,讓Thread2執行緒拿到obj2的鎖
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2){
System.out.println("Thread1 拿到了 obj2 的鎖!");
}
}
}
}
private static class Thread2 implements Runnable{
@Override
public void run() {
synchronized (obj2){
System.out.println("Thread2 拿到了 obj2 的鎖!");
try {
// 停頓2秒的意義在於,讓Thread1執行緒拿到obj1的鎖
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1){
System.out.println("Thread2 拿到了 obj1 的鎖!");
}
}
}
}
}