暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Java Agent

609



Hi~朋友,关注置顶防止错过消息


前言

之所以写关于Agent的东西,是因为最近在搭建公司的链路系统,在整个链路系统中我们会使用到各种各样的Agent,通过探针这种形式,避免了对业务代码的侵入性。

本篇只是开篇点题,实现一个统计方法的耗时时间的Agent,先直观感受一下Agent的作用,大量关于Agent的实现细节我们会在后续陆续更新。

什么是Java Agent?

Java Agent本质上就是一种代理程序,它会利用Java提供的Instrumentation的API来动态更改已经加载到JVM中的字节码,通常代理的工作需要我们来定义两个方法:

  • • premain(String agentArgs, Instrumentation inst):通过-javaagent指定jar,当应用程序启动时会静态进行静态加载,执行该方法

  • • agentmain(String agentArgs, Instrumentation inst):通过Java Attach API我们可以在程序运行时动态修改class

如何实现JavaAgent

  1. 1. 引入依赖


implementation 'javassist:javassist:3.12.1.GA'

  1. 1. 编写一段代码,如下该代码会在应用启动时,修改package session.controller下的所有class的method,统计每个方法的执行时长

public class Agent {

    public static void premain(String agentArgs, Instrumentation inst) {
        recordMethodCostTime(inst);
    }

    public static void agentmain(String agentArgs, Instrumentation inst) {
        recordMethodCostTime(inst);
        Class<?>[] classes = inst.getAllLoadedClasses();
        try {
            for (Class<?> c : classes) {
                if (c.isAnnotation() || c.isInterface() || c.isArray() || c.isEnum()) {
                    continue;
                }
                if (c.getName().startsWith("session.controller")) {
                    // 重新装载我们的类
                    inst.retransformClasses(c);
                }
            }
        } catch (UnmodifiableClassException e) {
            e.printStackTrace();
        }
    }

    private static void recordMethodCostTime(Instrumentation inst) {
        inst.addTransformer(new RecordCostTimeTransformer(), true);
    }

    public static class RecordCostTimeTransformer implements ClassFileTransformer {

        @Override
        public byte[] transform(
                ClassLoader loader,
                String className,
                Class<?> classBeingRedefined,
                ProtectionDomain protectionDomain,
                byte[] classfileBuffer)
                throws IllegalClassFormatException {
            byte[] byteCode = classfileBuffer;
            if (!className.startsWith("session/controller")) {
                return byteCode;
            }
            ClassPool cp = ClassPool.getDefault();
            try {
                CtClass ctClass = cp.makeClass(new ByteArrayInputStream(classfileBuffer));
                CtMethod[] methods = ctClass.getDeclaredMethods();
                for (CtMethod method : methods) {
                    method.addLocalVariable("startTime", CtClass.longType);
                    method.insertBefore("startTime = System.currentTimeMillis();");
                    method.addLocalVariable("endTime", CtClass.longType);
                    StringBuilder endBlock = new StringBuilder();
                    endBlock.append("endTime = System.currentTimeMillis();");
                    method.addLocalVariable("costTime", CtClass.longType);
                    endBlock.append("costTime = endTime - startTime;");
                    endBlock.append(
                            "System.out.println(\"method: "
                                    + method.getName()
                                    + ", execute cost time:\" + costTime +\" ms\");");
                    method.insertAfter(endBlock.toString());
                }
                byteCode = ctClass.toBytecode();
            } catch (IOException | CannotCompileException e) {
                throw new RuntimeException(e);
            }

            return byteCode;
        }
    }
}

  1. 1. 定义MAINIFEST.MF文件,内容如下:

Agent-Class: cn.sh.agent.Agent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: cn.sh.agent.Agent

如何静态加载Agent

在Java程序启动时,我们通过-javaagent指定Agent的jar包,如下:

-javaagent:/java-agent-1.0.0-SNAPSHOT.jar


如何动态加载Agent

通过动态加载Agent,我们可以对程序运行中的class进行重新加载,利用此功能我们可以对线上增加部分代码进行问题排查和定位。

public class DynamicAgent {

    public static void main(String[] args) {
        try {
            List<VirtualMachineDescriptor> virtualMachineDescriptors = VirtualMachine.list();
            for (VirtualMachineDescriptor descriptor : virtualMachineDescriptors) {
                if (descriptor.displayName().equals("session.DaemonApplication")) {
                    VirtualMachine jvm = VirtualMachine.attach(descriptor);
                    jvm.loadAgent(
                            "/Users/sh/workspace/java-knowledge-system/java-agent/build/libs/java-agent-1.0.0-SNAPSHOT.jar");
                    jvm.detach();
                }
            }
        } catch (AttachNotSupportedException
                | IOException
                | AgentLoadException
                | AgentInitializationException e) {
            throw new RuntimeException(e);
        }
    }
}

效果图如下:

源码地址https://github.com/echo9509/java-knowledge-system

本期文章就到这,扫码关注,更多内容我们下期再见!



文章转载自程序员修炼笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论