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进程

相关文章
相关标签/搜索