Java生產環境效能監控與調優—基於JDK命令列工具的監控

蔣老溼發表於2018-10-10

Java生產環境效能監控與調優—基於JDK命令列工具的監控

JVM引數型別

標準引數

基本上不變,相對比較穩定

  • -help
  • -server 、-client
  • -version 、-showversion
  • -cp 、-classpath

非標準化引數

在部分JVM裡面會有變化,但是變化小

  • -Xint:解釋執行
  • -Xcomp:第一次使用就編譯成原生程式碼
  • -Xmixed:混合模式,JVM自己來決定是否編譯成原生程式碼

Java生產環境效能監控與調優—基於JDK命令列工具的監控

XX引數

XX引數是非標準化引數、相對不穩定、主要用於JVM調優和Debug,分為2大類:

  • Boolean型別
    格式:-XX:[+-] 表示(+)啟用或者禁用(-)name屬性
    比如:-XX:+UseConMarkSweepGC
              -XX:UseG1GC
  • 非Boolean型別
    格式:-XX: = 表示name的屬性值是value
    比如:-XX:MaxGCPauseMillis = 200
              -XX:GCTimeRatio = 19

-Xmx(最大JVM記憶體)-Xms(最小JVM內在)
它不是X引數,而是XX引數
-Xms等價於-XX:InitialHeapSize
-Xmx等價於-XX:MaxHeapSize
在linux中檢視java程式記憶體大小 jinfo -flag MaxHeapSize [程式ID]

Java生產環境效能監控與調優—基於JDK命令列工具的監控

如何檢視JVM執行時引數

  • -XX:+PrintFlagsInitial 檢視初始時的一個值
  • -XX:+PrintFlagsFinal 檢視最終的一個值
  • -XX:+UnlockExperimentalVMOptions 解鎖實驗引數
  • -XX:+UnlockDiagnosticVMOptions 解鎖診斷引數
  • -XX:+PrintCommandLineFlags 列印命令列引數

例項:java -XX:+PrintFlagsFinal -version

Java生產環境效能監控與調優—基於JDK命令列工具的監控
Java生產環境效能監控與調優—基於JDK命令列工具的監控
=表示預設值
:=被使用者或者JVM修改後的值

jps

jps 命令類似與 linux 的 ps 命令,但是它只列出系統中所有的 Java 應用程式。 通過 jps 命令可以方便地檢視 Java 程式的啟動類、傳入引數和 Java 虛擬機器引數等資訊。 具體參考jvm 效能調優工具之jps

Java生產環境效能監控與調優—基於JDK命令列工具的監控

jinfo

檢視一個JVM正在執行的引數值

Java生產環境效能監控與調優—基於JDK命令列工具的監控
jinfo舉例

  • 檢視最大記憶體 jinfo -flag MaxHeapSize [程式ID]
    Java生產環境效能監控與調優—基於JDK命令列工具的監控
  • 檢視垃圾回收器 jinfo -flag UseConcMarkSweepGC/UseG1GC/UseParallelGC [程式ID]
    Java生產環境效能監控與調優—基於JDK命令列工具的監控

使用jstat檢視jvm統計資訊

JDK Tools and Utilities

類載入資訊

部分options: -class, -compiler,-gc, -printcompilation 更多可點此處檢視

Java生產環境效能監控與調優—基於JDK命令列工具的監控

垃圾收集資訊

部分options: -gc, -gcutil,-gccause, -gcnew, -gcold 更多可點此處檢視

Java生產環境效能監控與調優—基於JDK命令列工具的監控
-gc輸出結果引數說明:

  • S0C、S1C、S0U:S0和S1的總量與使用量
  • EC、EU:Eden區總量和使用量
  • OC、OU:Old區總量和使用量
  • MC、MU:Metaspace區總量和使用量
  • CCSC、CCSU:壓縮類空間總量和使用量
  • YGC、YGCT:YoungGC的次數與時間
  • FGC、FGCT:FullGC的次數與時間
  • GCT:總的GC時間
    Java生產環境效能監控與調優—基於JDK命令列工具的監控

JIT編譯資訊

部分options: -compiler, -printcompilation 更多可點此處檢視

Java生產環境效能監控與調優—基於JDK命令列工具的監控

jmap+MAT分析記憶體溢位

例項測試專案基於spring boot快速搭建
User.java

public class User{
    private int id;
    private String name; 
    # 構造方法
    # get() and set()
}
複製程式碼

MemoryController.java

@RestController
public class MemoryController{
    private List<User> userList = new ArrayList<User>();
    
    /**
    * 設定堆最大最小記憶體,方便快速除錯(-Xmx32M  -Xms32M)
    **/
    @GetMapping("/heap")     ##基於堆的記憶體溢位
    public String heap(){
        int i = 0;
        while(true){
            userList.add(new User(i++, UUID.randomUUID().toString()));
        }
    }
    
    /**
    * 設定非堆最大最小記憶體(-XX:MetaspaceSize=32M  -XX:MaxMetaspaceSize=32M)
    **/
    @GetMapping("/noheap")   ## 非堆的記憶體溢位
    public String noheap(){
        while(true){    ##基於動態生成class測試
            classList.addAll(Metaspace.createClasses());
        }
    }
}
複製程式碼

Metaspace.java

import java.util.ArrayList;
import java.util.List;
 
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
 
/*
 * 繼承ClassLoader是為了方便呼叫defineClass方法,因為該方法的定義為protected
 * */
public class Metaspace extends ClassLoader {
    ## 類持有
    List<Class<?>> classes = new ArrayList<Class<?>>();
    ## 迴圈1000w次生成1000w個不同的類。
    for (int i = 0; i < 10000000; ++i) {
        ClassWriter cw = new ClassWriter(0);
        ## 定義一個類名稱為Class{i},它的訪問域為public,父類為java.lang.Object,不實現任何介面
        cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
        ## 定義建構函式<init>方法
        MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>","()V", null, null);
        ## 第一個指令為載入this
        mw.visitVarInsn(Opcodes.ALOAD, 0);
        ## 第二個指令為呼叫父類Object的建構函式
        mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        ## 第三條指令為return
        mw.visitInsn(Opcodes.RETURN);
        mw.visitMaxs(1, 1);
        mw.visitEnd();

        Metaspace test = new Metaspace();
        byte[] code = cw.toByteArray();
        ## 定義類
        Class<?> exampleClass = test.defineClass("Class" + i, code, 0, code.length);
        classes.add(exampleClass);
    }
}
複製程式碼

訪問路徑localhost:8080/heap 堆記憶體溢位圖示

Java生產環境效能監控與調優—基於JDK命令列工具的監控
訪問路徑localhost:8080/noheap 非堆記憶體溢位圖示
Java生產環境效能監控與調優—基於JDK命令列工具的監控

匯出應用程式記憶體映像檔案

有2中方式可以匯出,分別是:記憶體溢位自動匯出、使用jmap命令手動匯出

  • 記憶體溢位自動匯出——使用如下jvm引數選項
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./     ##要匯出的檔案路徑
複製程式碼

Java生產環境效能監控與調優—基於JDK命令列工具的監控
Java生產環境效能監控與調優—基於JDK命令列工具的監控
Java生產環境效能監控與調優—基於JDK命令列工具的監控

  • 使用jmap命令手動匯出 部分options: -heap, -clstats, -dump:, -F 更多可點此處檢視
    資料過大可能無法匯出。
    下圖示例:
    Java生產環境效能監控與調優—基於JDK命令列工具的監控

使用MAT分析定位錯誤

MAT安裝及使用教程
記憶體分析工具 MAT 的使用
MAT使用進階

jstack實戰執行緒異常

Java的堆疊跟蹤 - 為給定的程式或核心檔案或遠端除錯伺服器列印執行緒的堆疊跟蹤。

選項 說明
-F 當jstack[ -l] pid沒有響應時強制執行堆疊轉儲。
-l 列印有關鎖的其他資訊,例如擁有的可擁有java.util.concurrent同步器列表
-m 列印具有Java和本機C / C ++幀的混合模式堆疊跟蹤。
-H 列印幫助資訊。
-help 列印幫助資訊。

java執行緒的狀態

文獻參考地址
下表列出了使用Control + Break Handler進行執行緒轉儲的可能執行緒狀態。

執行緒狀態 描述
NEW 該主題尚未開始。
RUNNABLE 執行緒正在JVM中執行。
BLOCKED 執行緒被阻塞等待監視器鎖定。
WAITING 執行緒無限期地等待另一個執行緒執行特定操作。
TIMED_WAITING 執行緒正在等待另一個執行緒執行最多指定等待時間的操作。
TERMINATED 執行緒已經退出。

執行緒狀態流轉圖

Java生產環境效能監控與調優—基於JDK命令列工具的監控

例項-CPU飆高

CpuController.java

import java.util.ArrayList;
import java.util.List;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CpuController {
	
	/**
	 * 死迴圈
	 * */
	@RequestMapping("/loop")
	public List<Long> loop(){
		String data = "{\"data\":[{\"partnerid\":]";
		return getPartneridsFromJson(data);
	}
	
	private Object lock1 = new Object();
	private Object lock2 = new Object();
	
	/**
	 * 死鎖
	 * */
	@RequestMapping("/deadlock")
	public String deadlock(){
		new Thread(()->{
			synchronized(lock1) {
				try {Thread.sleep(1000);}catch(Exception e) {}
				synchronized(lock2) {
					System.out.println("Thread1 over");
				}
			}
		}) .start();
		new Thread(()->{
			synchronized(lock2) {
				try {Thread.sleep(1000);}catch(Exception e) {}
				synchronized(lock1) {
					System.out.println("Thread2 over");
				}
			}
		}) .start();
		return "deadlock";
	}
	public static List<Long> getPartneridsFromJson(String data){  
	    ##{\"data\":[{\"partnerid\":982,\"count\":\"10000\",\"cityid\":\"11\"},{\"partnerid\":983,\"count\":\"10000\",\"cityid\":\"11\"},{\"partnerid\":984,\"count\":\"10000\",\"cityid\":\"11\"}]}  
	    ##上面是正常的資料  
	    List<Long> list = new ArrayList<Long>(2);  
	    if(data == null || data.length() <= 0){  
	        return list;  
	    }      
	    int datapos = data.indexOf("data");  
	    if(datapos < 0){  
	        return list;  
	    }  
	    int leftBracket = data.indexOf("[",datapos);  
	    int rightBracket= data.indexOf("]",datapos);  
	    if(leftBracket < 0 || rightBracket < 0){  
	        return list;  
	    }  
	    String partners = data.substring(leftBracket+1,rightBracket);  
	    if(partners == null || partners.length() <= 0){  
	        return list;  
	    }  
	    while(partners!=null && partners.length() > 0){  
	        int idpos = partners.indexOf("partnerid");  
	        if(idpos < 0){  
	            break;  
	        }  
	        int colonpos = partners.indexOf(":",idpos);  
	        int commapos = partners.indexOf(",",idpos);  
	        if(colonpos < 0 || commapos < 0){  
	            //partners = partners.substring(idpos+"partnerid".length()); #註釋該部分程式碼丟擲問題
	            continue;
	        }  
	        String pid = partners.substring(colonpos+1,commapos);  
	        if(pid == null || pid.length() <= 0){  
	            //partners = partners.substring(idpos+"partnerid".length()); #註釋該部分程式碼丟擲問題
	            continue;
	        }  
	        try{  
	            list.add(Long.parseLong(pid));  
	        }catch(Exception e){  
	            //do nothing  
	        }  
	        partners = partners.substring(commapos);  
	    }  
	    return list;  
	}  
}
複製程式碼

top命令查詢Linux cpu

Java生產環境效能監控與調優—基於JDK命令列工具的監控
匯出檔案 jstack [程式ID] > [fileName]
對匯出後的檔案內容進行分析定位,可以參考
【JVM效能調優】jstack和執行緒dump分析
java運維 jstack dump日誌檔案詳解

輸出所有執行緒 top -p [程式ID] -H

Java生產環境效能監控與調優—基於JDK命令列工具的監控

相關文章