一、性能监控:
要监控应用的性能,首先列出性能监控点(比如方法执行耗时,sql执行耗时等等监控),然后输出要关注的信息,最终根据信息进行数据分析得出性能瓶颈后进行持续优化改进,不同应用、不同场景下,监控点不尽相同,要关注的信息如何获取却是每个工程师都要思考的问题。
在接触javassist与javaagent之前,有过几个方案,但发布了几版后发现实现方式太low、成本高、效率低等不足,其中包括:代码中嵌入日志、使用spring管理应用并使用aop、修改jar包源代码增加日志。
二、字节码插桩技术实现更高效的应用性能监控,什么是字节码插桩技术:
javaagent 是java1.5之后引⼊的特性,其主要作⽤是在class 被加载之前对其拦截,插⼊字节码。
javassist:编辑和创建字节码的类库,其主要的优点,在于简单,⽽且快速。直接使⽤java编码的形式。
基于javaagent和javassist实现字节码插桩技术。
三、demo实现方法执行耗时监控
public class TimeAgent {
public static void premain(String args, Instrumentation instrumentation) {
System.out.println("传入参数:"+args);
instrumentation.addTransformer(new ClassFileTransformer() {
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
throws IllegalClassFormatException {
if (!"com/apm/agent/TimeServiceImpl".equals(className)) {
return null;
}
try {
//javassist 编辑改造字节码
ClassPool classPool = new ClassPool();
classPool.insertClassPath(new LoaderClassPath(loader));
CtClass ctClass = classPool.get("com.apm.agent.TimeServiceImpl");
CtMethod method = ctClass.getDeclaredMethod("hello");
method.addLocalVariable("beginTime",CtClass.longType);
method.addLocalVariable("endTime", CtClass.longType);
method.insertBefore("System.out.println(System.currentTimeMillis());");
method.insertBefore("long beginTime = System.currentTimeMillis();");
method.insertAfter("long endTime = System.currentTimeMillis();\n"+
" System.out.println(endTime-beginTime);");
return ctClass.toBytecode();
} catch (NotFoundException e) {
e.printStackTrace();
} catch (CannotCompileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
});
}
}
public class TimeServiceImpl {
public TimeServiceImpl() {}
//有一次必须向上一层抛出,如果用try{}catch(){}捕获异常,则会抛出异常
// java.lang.VerifyError: (class: com/apm/agent/TimeServiceImpl, method: hello signature:
// (Ljava/lang/String;)V) Register pair 3/4 contains wrong type
public void hello(String hello) throws InterruptedException {
{ // 开始时间
Thread.sleep(3000);
// 结束时间
}
}
}
public class TimeAgentTest {
public static void main(String[] args) throws InterruptedException {
System.out.println("执行main方法");
TimeServiceImpl timeService = new TimeServiceImpl();
timeService.hello("");
}
}
编辑MANIFEST.MF文件
设置JVM启动参数
执行TimeAgentTest.main()方法,输出结果:
传入参数:helloworld
执行main方法
1554281967837
3001