Java性能监控之javassist探索
由于个人工作原因,近期遇到多起因应用性能导致业务中断的事情。多次排查分析总结,发现是应用性能问题,当然性能的提现是多维度的,在这里就不赘述了。
主要关注在应用运行中断之前就发现它(事前处理),是很重要的。
要监控应用的性能,首先列出性能监控点,然后输出要关注的信息,最终根据信息进行数据分析得出性能瓶颈后进行持续优化改进,在问题爆发前将其扼杀在“子宫”里。
不同应用、不同场景下,监控点不尽相同,要关注的信息如何获取却是每个工程师都要思考的问题。
在接触javassist之前,有过几个方案,但发布了几版后发现实现方式太low、成本高、效率低等不足,其中包括:代码中嵌入日志、使用spring管理应用并使用aop、修改jar包源代码增加日志。
如果是一个新搭建的工程,以上方案可以在框架搭建过程中便包含进去,作为框架的基础能力随同应用一起发布。但是平台上应用很多,明显不是很适用,并且有些操作所带来的风险需要更多的工作量去规避。
于是,找到了她------javassist。
javassist的使用要借助于javaagent技术,接下来介绍如何使用javassist
1、获取javassit-3.20.0-GA.jar
2、创建类AgentTransformer并实现ClassFileTransformer接口,使用javassist API完成对源类字节码级别的修改
3、创建类AgentDemo,并增加premain实现public static void premain(String args, Instrumentation inst){ inst.addTransformer(new AgentTransformer()); }
4、创建MAINFEST.MF文件,内容如下:
Manifest-Version: 1.0
Premain-Class: AgentDemo
Can-Redefine-Classes: true
Can-Retransform-Classes: true
5、打jar包:main class指定使用MAINFEST.MF文件
6、实际应用--创建demo应用,引入两个jar包:javassist-3.20.0-GA.jar和javassistdemo.jar(上面打包出来的)
7、修改java启动参数
8、运行应用程序,观察结果
System.out.println("This code is inserted before constructor sun/misc/URLClassPath$FileLoader$1");
System.out.println("This code is inserted after constructor sun/misc/URLClassPath$FileLoader$1");
This code is inserted before constructor sun/misc/URLClassPath$FileLoader$1
This code is inserted after constructor sun/misc/URLClassPath$FileLoader$1
System.out.println("This code is inserted before constructor com/hope/javassistapp/app/App");
System.out.println("This code is inserted after constructor com/hope/javassistapp/app/App");
System.out.println("This code is inserted before constructor sun/misc/Cleaner");
System.out.println("This code is inserted after constructor sun/misc/Cleaner");
System.out.println("This code is inserted before constructor java/lang/Enum");
System.out.println("This code is inserted after constructor java/lang/Enum");
This code is inserted before constructor sun/misc/URLClassPath$FileLoader$1
This code is inserted after constructor sun/misc/URLClassPath$FileLoader$1
System.out.println("This code is inserted before constructor com/hope/javassistapp/construct/JavassistDemo1");
System.out.println("This code is inserted after constructor com/hope/javassistapp/construct/JavassistDemo1");
This code is inserted before constructor com/hope/javassistapp/construct/JavassistDemo1
JavassistDemo1:自身构造函数输出内容
This code is inserted after constructor com/hope/javassistapp/construct/JavassistDemo1
This code is inserted before constructor sun/misc/URLClassPath$FileLoader$1
This code is inserted after constructor sun/misc/URLClassPath$FileLoader$1
System.out.println("This code is inserted before constructor com/hope/javassistapp/construct/JavassistDemo2");
System.out.println("This code is inserted after constructor com/hope/javassistapp/construct/JavassistDemo2");
This code is inserted before constructor com/hope/javassistapp/construct/JavassistDemo2
JavassistDemo2:自身构造函数输出内容
This code is inserted after constructor com/hope/javassistapp/construct/JavassistDemo2
System.out.println("This code is inserted before constructor java/lang/Shutdown");
System.out.println("This code is inserted after constructor java/lang/Shutdown");
System.out.println("This code is inserted before constructor java/lang/Shutdown$Lock");
System.out.println("This code is inserted after constructor java/lang/Shutdown$Lock");
This code is inserted before constructor java/lang/Shutdown$Lock
This code is inserted before constructor java/lang/Shutdown$Lock
This code is inserted after constructor java/lang/Shutdown$Lock
This code is inserted after constructor java/lang/Shutdown$Lock
This code is inserted before constructor java/lang/Shutdown$Lock
This code is inserted before constructor java/lang/Shutdown$Lock
This code is inserted after constructor java/lang/Shutdown$Lock
This code is inserted after constructor java/lang/Shutdown$Lock
输出结果中,黄色高亮部分是动态增加的代码造成的效果。
AgentDemo和AgentTransformer是agent工程下的,用于动态修改类使用。
package com.hope.agent; import java.lang.instrument.Instrumentation; import com.hope.transform.AgentTransformer; /** * java agent 入口 * @author hp * */ public class AgentDemo { public static void premain(String args, Instrumentation inst) { inst.addTransformer(new AgentTransformer()); } }
package com.hope.transform; import java.io.ByteArrayInputStream; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import javassist.LoaderClassPath; /** * 对类字节码转译 * @author hp * */ public class AgentTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { /** * 此处使用javassist API对classfileBuffer进行修改 */ ClassPool pool = new ClassPool(true); pool.appendClassPath(new LoaderClassPath(loader)); try { CtClass cls = pool.makeClass(new ByteArrayInputStream(classfileBuffer)); //获取构造函数数组 CtConstructor[] ccs = cls.getDeclaredConstructors(); //构造函数方法体开始时添加的代码 String codeStrBefore = "System.out.println(\"This code is inserted before constructor "+className+"\");"; System.out.println(codeStrBefore); //构造函数方法体结束前添加的代码 String codeStrAfter = "System.out.println(\"This code is inserted after constructor "+className+"\");"; System.out.println(codeStrAfter); for (CtConstructor cc : ccs) { cc.insertBefore(codeStrBefore); cc.insertAfter(codeStrAfter, true); } return cls.toBytecode(); } catch (IOException e) { e.printStackTrace(); } catch (RuntimeException e) { e.printStackTrace(); } catch (CannotCompileException e) { e.printStackTrace(); } return null; } }
App和JavassistDemo1、JavassistDemo2用于demo演示使用
package com.hope.javassistapp.app; import com.hope.javassistapp.construct.JavassistDemo1; import com.hope.javassistapp.construct.JavassistDemo2; public class App { /** * @param args */ public static void main(String[] args) { JavassistDemo1 d1 = new JavassistDemo1(); JavassistDemo2 d2 = new JavassistDemo2(); } }
package com.hope.javassistapp.construct; public class JavassistDemo1 { public JavassistDemo1() { System.out.println("JavassistDemo1:自身构造函数输出内容"); } }
package com.hope.javassistapp.construct; public class JavassistDemo2 { public JavassistDemo2() { System.out.println("JavassistDemo2:自身构造函数输出内容"); } }
对于javassist目前探索到这里,日后还会继续加深对其理解和使用,敬请期待。
欢迎业内人士交流经验。