java-性能排查工具

性能排查工具:

第三方:arthas使用和原理以及btrace、housemd、greys等性能排查工具、在线gc分析工具(gceasy)

jdk:

jconsole、jvisualvm、jmc

参考:

https://arthas.aliyun.com/doc/

https://www.cnblogs.com/fengzheng/p/7416942.html

https://github.com/oldmanpushcart/greys-anatomy

https://github.com/CSUG/HouseMD

https://gceasy.io/

https://blog.csdn.net/qq_40180411/article/details/103893219

Arthas
1.安装
//下载jar包
sudo curl -O https://arthas.aliyun.com/arthas-boot.jar

//运行jar包
sudo -u tomcat java -jar arthas-boot.jar

2.常用命令
dashboard:jvm实时面板
-i:刷新实时数据间隔时间
-n:刷新实时数据的次数

ID: Java级别的线程ID,注意这个ID不能跟jstack中的nativeID一一对应。

NAME: 线程名

GROUP: 线程组名

PRIORITY: 线程优先级, 1~10之间的数字,越大表示优先级越高

STATE: 线程的状态

RUNNABLE 线程运行中或I/O等待
BLOCKED 线程在等待monitor锁(synchronized关键字)
TIMED_WAITING 线程在等待唤醒,但设置了时限
WAITING 线程在无限等待唤醒
CPU%: 线程的cpu使用率。比如采样间隔1000ms,某个线程的增量cpu时间为100ms,则cpu使用率=100/1000=10%

DELTA_TIME: 上次采样之后线程运行增量CPU时间,数据格式为秒

TIME: 线程运行总CPU时间,数据格式为分:秒

INTERRUPTED: 线程当前的中断位状态

DAEMON: 是否是daemon线程

heap:堆
nonheap:方法区(类信息,常量(常量池),静态变量)
metaspace:类的元数据存储
thead:线程信息
-n:打印n个耗时最多的线程
-b:打印阻塞其他线程的线程
sc(search-class):查找类 可以通配符找到全限定名
monitor:方法耗时监控
-c:统计周期(默认120s)

watch:查看出入参数
-n:执行次数

3.原理
Instrument和Attach API
Jdk5增加了一个包java.lang.instrument,提供了对Jvm底层组件的访问能力,Instrument要求在运行前利用命令行参数或者系统参数设置代理类,VM启动完成之后(绝大多数类加载前)初始化。

开发基于instrument的应用,需要这么几个步骤:

编写premain函数
jar文件打包,制定Premain-Class
使用-javaagent参数启动

jdk5只有在main运行之前进行访问

Jdk6以后,针对这点进行了改进,开发者可以在main函数执行之后再启动自己的Instrument应用,入口是agentmain函数。arthas就是通过这个实现的。

之后就可以通过addTransformer,retransformClasses,redefineClasses等方式对字节码进行增强和热替换了。

ASM

ASM是一个Java字节码操作框架,用来动态生成class或者增强class,cglib的底层就是它,arthas也是通过它实现对class的增强的。通过AOP实现

cglib : ASM框架对字节码文件进行修改,主要适用于类
jdk动态代理 :使用反射生成一个代理接口的匿名类,主要适用于接口的实现类。(因为生成的代理类继承了proxy.java)

JPDA
https://juejin.cn/post/6844903608522113038

JVMTI又是在JPDA(Java Platform Debugger Architecture)之下的三层架构之一,JVMTI(JVM Tool Interface)、JDWP(Java Debug Wire Protocol)、JDI(Java Debug Interface)

周方鹏 > 性能排查工具 > 1595409-20190921212320818-499259713.png

IDEA的debug也就是使用jpda架构的功能来实现的

agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8080,其实就是加载了jdwp的lib,开启了调试端口,然后就可以通过JDI接口进行交互调试。
Demo


https://github.com/dengshiwei/asm-module

package Demo;

import jdk.internal.org.objectweb.asm.*;
import jdk.internal.org.objectweb.asm.commons.AdviceAdapter;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;

public class CostTime {

    public static void main(String[] args) {
        redefinePersonClass();
    }

    private static void redefinePersonClass() {
        String className = "Demo.AsmDemo";
        try {
            InputStream inputStream = new FileInputStream("C:\\Users\\fangpengx.zhou\\Desktop\\test\\out\\production\\test\\Demo\\AsmDemo.class");
            // 1. 创建 ClassReader 读入 .class 文件到内存中
            ClassReader reader = new ClassReader(inputStream);
            // 2. 创建 ClassWriter 对象,将操作之后的字节码的字节数组回写
            ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
            // 3. 创建自定义的 ClassVisitor 对象
            ClassVisitor change = new ChangeVisitor(writer);
            // 4. 将 ClassVisitor 对象传入 ClassReader 中
            reader.accept(change, ClassReader.EXPAND_FRAMES);

            Class clazz = new MyClassLoader().defineClasses(className, writer.toByteArray());
            Object personObj = clazz.newInstance();
            Method nameMethod = clazz.getDeclaredMethod("printHelloWorld", null);
            nameMethod.invoke(personObj, null);
            // 获取修改后的 class 文件对应的字节数组
            System.out.println("Success!");
            byte[] code = writer.toByteArray();
            try {
                // 将二进制流写到本地磁盘上
                FileOutputStream fos = new FileOutputStream("C:\\Users\\fangpengx.zhou\\Desktop\\HelloWorld.class");
                fos.write(code);
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Failure!");
        }
    }

    static class ChangeVisitor extends ClassVisitor {

        ChangeVisitor(ClassVisitor classVisitor) {
            super(Opcodes.ASM5, classVisitor);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions);
            if (name.equals("<init>")) {
                return methodVisitor;
            }
            return new ChangeAdapter(Opcodes.ASM4, methodVisitor, access, name, desc);
        }
    }

    static class ChangeAdapter extends AdviceAdapter {
        private int startTimeId = -1;

        private String methodName = null;

        ChangeAdapter(int api, MethodVisitor mv, int access, String name, String desc) {
            super(api, mv, access, name, desc);
            methodName = name;
        }

        @Override
        protected void onMethodEnter() {
            super.onMethodEnter();
            startTimeId = newLocal(Type.LONG_TYPE);
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            mv.visitIntInsn(LSTORE, startTimeId);
        }

        @Override
        protected void onMethodExit(int opcode) {
            super.onMethodExit(opcode);
            int durationId = newLocal(Type.LONG_TYPE);
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            mv.visitVarInsn(LLOAD, startTimeId);
            mv.visitInsn(LSUB);
            mv.visitVarInsn(LSTORE, durationId);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
            mv.visitInsn(DUP);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
            mv.visitLdcInsn("The cost time of " + methodName + " is ");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv.visitVarInsn(LLOAD, durationId);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        }
    }

}










//结果
"C:\Program Files\Java\jdk1.8.0_261\bin\java.exe" "-javaagent:C:\Users\fangpengx.zhou\IntelliJ IDEA 2020.1.2\lib\idea_rt.jar=14144:C:\Users\fangpengx.zhou\IntelliJ IDEA 2020.1.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_261\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\rt.jar;C:\Users\fangpengx.zhou\Desktop\test\out\production\test" Demo.CostTime
hello world
The cost time of printHelloWorld is 1001
Success!

Process finished with exit code 0



//反编译
package Demo;

public class AsmDemo {
    public AsmDemo() {
    }

    public void printHelloWorld() {
        long var1 = System.currentTimeMillis();

        try {
            String s = "hello world";
            Thread.sleep(1000L);
            System.out.println(s);
        } catch (Exception var6) {
            var6.printStackTrace();
        }

        long var4 = System.currentTimeMillis() - var1;
        System.out.println("The cost time of printHelloWorld is " + var4);
    }
}









//查看线程信息
package Demo.test;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryUsage;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

public class MultiThread {

    public static void main(String[] args){
        //获取java的线程管理MXBean
        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
        //不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息
        ThreadInfo[] threadInfo = threadBean.dumpAllThreads(false, false);
        //遍历线程信息,仅打印线程id和线程名称信息
        for(ThreadInfo info : threadInfo){
            System.out.println(info.getThreadId() + "--" + info.getThreadName() +"--"+ info.getThreadState().name());
        }

       //jvm内存MXbEAN
        MemoryUsage heapMemoryUsage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
        System.out.println("jvm.heap.init is " + (heapMemoryUsage.getInit()));
        System.out.println("jvm.heap.used is " + (heapMemoryUsage.getUsed()));
        System.out.println("jvm.heap.committed is " + (heapMemoryUsage.getCommitted()));
        System.out.println("jvm.heap.max is " + (heapMemoryUsage.getMax()));
    }

}


//结果
"C:\Program Files\Java\jdk1.8.0_261\bin\java.exe" "-javaagent:C:\Users\fangpengx.zhou\IntelliJ IDEA 2020.1.2\lib\idea_rt.jar=2050:C:\Users\fangpengx.zhou\IntelliJ IDEA 2020.1.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_261\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\rt.jar;C:\Users\fangpengx.zhou\Desktop\test\out\production\test" Demo.test.MultiThread
6--Monitor Ctrl-Break--RUNNABLE
5--Attach Listener--RUNNABLE
4--Signal Dispatcher--RUNNABLE
3--Finalizer--WAITING
2--Reference Handler--WAITING
1--main--RUNNABLE
jvm.heap.init is 268435456
jvm.heap.used is 5381192
jvm.heap.committed is 257425408
jvm.heap.max is 3797417984

Process finished with exit code 0


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值