性能排查工具:
第三方: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)
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