1. 程式人生 > >jvm優化_day01

jvm優化_day01

jvm的三種執行引數
jvm的優化的作用:
	讓程式能更好更快的跑起來,解決生產環境下出現的bug

jvm的三種執行引數:
1. 標準引數: 不會隨著jdk版本的改變而變化
-version         #jdk版本
-showversion     #輸出jdk版本資訊,並繼續執行後面的命令,在除錯時非常有用
-help			#檢索出所有的標準引數
-D			    #設定jvm的屬性引數,eg: -Dstr=hello
-server			#以更多的初始堆記憶體來啟動服務,使用並行垃圾回收器,啟動速度較慢,只支援64位的作業系統
-client			#以較少的初始堆記憶體來啟動服務,
使用序列垃圾回收器,啟動速度快,3264位都支援 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 的鎖!");
                }
            }
        }
    }
}