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

著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

JVM參數類型

標準參數

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

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

非標準化參數

在部分JVM裏面會有變化,可是變化小java

  • -Xint:解釋執行
  • -Xcomp:第一次使用就編譯成本地代碼
  • -Xmixed:混合模式,JVM本身來決定是否編譯成本地代碼

XX參數

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

  • 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] web

如何查看JVM運行時參數

  • -XX:+PrintFlagsInitial 查看初始時的一個值
  • -XX:+PrintFlagsFinal 查看最終的一個值
  • -XX:+UnlockExperimentalVMOptions 解鎖實驗參數
  • -XX:+UnlockDiagnosticVMOptions 解鎖診斷參數
  • -XX:+PrintCommandLineFlags 打印命令行參數

實例:java -XX:+PrintFlagsFinal -versionspring

=表示默認值
:=被用戶或者JVM修改後的值

jps

jps 命令相似與 linux 的 ps 命令,可是它只列出系統中全部的 Java 應用程序。 經過 jps 命令能夠方便地查看 Java 進程的啓動類、傳入參數和 Java 虛擬機參數等信息。 具體參考jvm 性能調優工具之jps tomcat

jinfo

查看一個JVM正在運行的參數值 bash

jinfo舉例

  • 查看最大內存 jinfo -flag MaxHeapSize [進程ID]
  • 查看垃圾回收器 jinfo -flag UseConcMarkSweepGC/UseG1GC/UseParallelGC [進程ID]

使用jstat查看jvm統計信息

JDK Tools and Utilities服務器

類加載信息

部分options: -class, -compiler,-gc, -printcompilation 更多可點此處查看 oracle

垃圾收集信息

部分options: -gc, -gcutil,-gccause, -gcnew, -gcold 更多可點此處查看 app

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

JIT編譯信息

部分options: -compiler, -printcompilation 更多可點此處查看

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 堆內存溢出圖示

訪問路徑 localhost:8080/noheap 非堆內存溢出圖示

導出應用程序內存映像文件

有2中方式能夠導出,分別是:內存溢出自動導出、使用jmap命令手動導出

  • 內存溢出自動導出——使用以下jvm參數選項
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./     ##要導出的文件路徑
複製代碼

  • 使用jmap命令手動導出 部分options: -heap, -clstats, -dump:, -F 更多可點此處查看
    數據過大可能沒法導出。
    下圖示例:

使用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 線程已經退出。

線程狀態流轉圖

實例-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

導出文件 jstack [進程ID] > [fileName]
對導出後的文件內容進行分析定位,能夠參考
【JVM性能調優】jstack和線程dump分析
java運維 jstack dump日誌文件詳解

輸出全部線程 top -p [進程ID] -H

基於JVisualVM的可視化監控

監控本地tomcat

查看死鎖,循環

查看熱點方法執行時間

實時內存

修改插件中心地址

要選擇jdk版本對應插件中心的地址

監控遠程tomcat

監控普通java進程

相關文章
相關標籤/搜索