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());
    }
}
AgentDemo
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;
    }

}
AgentTransformer

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();
    }

}
App
package com.hope.javassistapp.construct;

public class JavassistDemo1 {

    public JavassistDemo1() {
        
        
        System.out.println("JavassistDemo1:自身构造函数输出内容");
        
        
    }
}
JavassistDemo1
package com.hope.javassistapp.construct;

public class JavassistDemo2 {

    public JavassistDemo2() {
        
        
        System.out.println("JavassistDemo2:自身构造函数输出内容");
        
        
    }
}
JavassistDemo2

对于javassist目前探索到这里,日后还会继续加深对其理解和使用,敬请期待。

欢迎业内人士交流经验。

原文地址:https://www.cnblogs.com/orionhp/p/6362615.html