如何用Java Agent实现无侵入的监控?

wen java案例 55

Java Agent实现无侵入监控

Java Agent允许在JVM层面拦截和修改字节码,实现无侵入的监控,主要思路是通过字节码增强技术在方法执行前后插入监控代码。

如何用Java Agent实现无侵入的监控?

核心实现方案

// 1. 创建Agent入口类
public class MonitorAgent {
    // premain方式 - 启动时加载
    public static void premain(String args, Instrumentation inst) {
        System.out.println("Monitor Agent premain called");
        inst.addTransformer(new MethodMonitorTransformer(), true);
    }
    // agentmain方式 - 运行时加载
    public static void agentmain(String args, Instrumentation inst) {
        System.out.println("Monitor Agent agentmain called");
        inst.addTransformer(new MethodMonitorTransformer(), true);
    }
}
// 2. 实现ClassFileTransformer
public class MethodMonitorTransformer implements ClassFileTransformer {
    private static final Set<String> MONITORED_PACKAGES = new HashSet<>();
    static {
        MONITORED_PACKAGES.add("com/example/service");
        MONITORED_PACKAGES.add("com/example/controller");
    }
    @Override
    public byte[] transform(ClassLoader loader, String className, 
                           Class<?> classBeingRedefined, 
                           ProtectionDomain protectionDomain, 
                           byte[] classfileBuffer) throws IllegalClassFormatException {
        // 只监控指定包下的类
        if (!shouldMonitor(className)) {
            return null;
        }
        try {
            return enhanceClass(className, classfileBuffer);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    private boolean shouldMonitor(String className) {
        return MONITORED_PACKAGES.stream()
            .anyMatch(pkg -> className != null && className.startsWith(pkg));
    }
    private byte[] enhanceClass(String className, byte[] classfileBuffer) {
        // 使用ASM进行字节码增强
        ClassReader cr = new ClassReader(classfileBuffer);
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = new MonitorClassVisitor(cw, className);
        cr.accept(cv, ClassReader.EXPAND_FRAMES);
        return cw.toByteArray();
    }
}

ASM字节码增强实现

// 3. 方法级别的监控增强
public class MonitorMethodVisitor extends MethodVisitor {
    private final String className;
    private final String methodName;
    private final String methodDesc;
    public MonitorMethodVisitor(MethodVisitor mv, String className, 
                                String methodName, String methodDesc) {
        super(Opcodes.ASM7, mv);
        this.className = className;
        this.methodName = methodName;
        this.methodDesc = methodDesc;
    }
    @Override
    public void visitCode() {
        // 方法开始前插入监控代码
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, 
            "com/example/monitor/MonitorCollector", 
            "start", 
            "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", 
            false);
        super.visitCode();
    }
    @Override
    public void visitInsn(int opcode) {
        // 在return指令前插入监控代码
        if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) 
            || opcode == Opcodes.ATHROW) {
            mv.visitMethodInsn(Opcodes.INVOKESTATIC,
                "com/example/monitor/MonitorCollector",
                "end",
                "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
                false);
        }
        super.visitInsn(opcode);
    }
}
// 4. 监控数据收集器
public class MonitorCollector {
    private static final Map<String, MethodMetrics> metricsMap = new ConcurrentHashMap<>();
    private static final ThreadLocal<Long> startTime = new ThreadLocal<>();
    public static void start(String className, String methodName, String desc) {
        startTime.set(System.currentTimeMillis());
    }
    public static void end(String className, String methodName, String desc) {
        Long start = startTime.get();
        if (start == null) return;
        long duration = System.currentTimeMillis() - start;
        String key = className + "#" + methodName;
        metricsMap.compute(key, (k, v) -> {
            if (v == null) {
                return new MethodMetrics(duration, 1);
            }
            v.addDuration(duration);
            v.incrementCount();
            return v;
        });
        // 发送到监控系统
        reportToMonitorSystem(className, methodName, duration);
    }
    // 获取监控数据
    public static Map<String, MethodMetrics> getMetrics() {
        return new HashMap<>(metricsMap);
    }
}

META-INF配置

META-INF/MANIFEST.MF中配置:

Manifest-Version: 1.0
Premain-Class: com.example.monitor.MonitorAgent
Agent-Class: com.example.monitor.MonitorAgent
Can-Retransform-Classes: true
Can-Redefine-Classes: true

使用方式

# 启动时加载
java -javaagent:monitor-agent.jar -jar application.jar
# 运行时加载(需要Attach API)
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent("monitor-agent.jar");
vm.detach();

高级功能扩展

// 5. 性能监控增强 - 统计P99延迟
public class PercentileMetrics {
    private final ConcurrentSkipListMap<Long, Integer> latencyMap = new ConcurrentSkipListMap<>();
    public long getLatencyAtPercentile(double percentile) {
        long total = getTotalCount();
        long target = (long) (total * percentile / 100);
        long cumulative = 0;
        for (Map.Entry<Long, Integer> entry : latencyMap.entrySet()) {
            cumulative += entry.getValue();
            if (cumulative >= target) {
                return entry.getKey();
            }
        }
        return 0;
    }
}
// 6. 异常监控
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExceptionMonitor {
    Class<? extends Throwable>[] value() default {Exception.class};
}
// 增强时检查注解
public class ExceptionMonitorAdvisor {
    public static void addExceptionMonitoring(ClassNode cn, MethodNode mn) {
        if (mn.visibleAnnotations != null) {
            for (AnnotationNode an : mn.visibleAnnotations) {
                if (an.desc.contains("ExceptionMonitor")) {
                    // 在方法开始和异常处理中添加监控
                }
            }
        }
    }
}

最佳实践建议

  1. 性能优化

    • 使用COMPUTE_MAXS而非COMPUTE_FRAMES减少计算开销
    • transform方法中尽早过滤不需要增强的类
    • 使用ThreadLocal减少锁竞争
  2. 稳定性保障

    • 添加增强失败的回退机制
    • 限制最大重试次数防止递归
    • 监控Agent本身的资源消耗
  3. 安全考虑

    • 对增强的类进行白名单过滤
    • 避免增强核心JDK类
    • 提供安全降级机制

这种方案可以做到业务代码零侵入,并且支持动态加载和卸载,非常适合生产环境的监控需求。

抱歉,评论功能暂时关闭!