Java Agent是Java应用程序运行时的一种扩展机制,通过加载系统进程,监控、操纵应用程序的运行过程,从而可以动态改变或增强应用程序的行为。Java Agent的实现原理可简要概述为通过在JVM启动时通过-agent参数来启动,加载指定的Jar包并通过预定义的Agentmain方法开启Agent的运行。下面我们来详细说明Java Agent的实现原理及使用攻略。
Java Agent的实现原理及使用攻略
Java Agent 的实现原理
Java Agent实现的主要功臣是Java Instrumentation API,该API允许在运行时通过代理机制对JVM进行编程控制。具体来说,Java Agent的实现原理分为以下几个步骤:
-
定义一个类,该类实现了premain或agentmain方法,并在Java应用程序启动时通过命令行参数指定启动。
-
装载Java Agent : 在Java应用程序启动的过程中,JVM会为Java Agent开辟一块独立的内存空间,并将Java Agent Jar包中定义的类文件加载到这个内存空间中。Java Agent使用了Java Instrumentation API来直接操作Java应用程序的字节码。Java Agent可以在一定程度上掌控Java应用程序的运行,并且以某种自己设定的方式进行切入,使Java应用程序运行时能够做出某些特定的响应。
-
在装载Java Agent的同时,JVM也会提供代理类加载器的实现,这个代理类加载器会加载Java Agent中的所有类。
-
在提供代理类加载器的同时,JVM也会提供Instrumentation API的实现,从而能够使Java Agent在运行时可以动态修改Java应用程序的字节码。
-
最后,Java Agent可以通过使用Javassist等工具对Java应用程序的字节码进行修改,从而对Java应用程序的行为进行操纵。
Java Agent 的使用攻略
Java Agent有两种常用的方案,一种是通过premain
方式加载Java Agent,一种是通过agentmain
方式加载Java Agent。
premain
方式
通过在JVM启动时,在命令行参数中指定Java Agent Jar包的路径,并使用premain
方法进行调用。
例如:
java -javaagent:/path/to/your/agent.jar com.example.MainClass
此时,我们需要在Java Agent Jar包中实现premain方法,并在premain方法中打印一条日志:
public static void premain(String args, Instrumentation instrumentation) {
System.out.println("Hello, This is Java Agent.");
}
agentmain
方式
通过在Java应用程序运行时,调用agentmain
方法进行Java Agent的加载。
例如:
public class MainClass {
public static void main(String[] args) throws Exception {
String agentJarPath = "/path/to/your/agent.jar";
String pid = getPid();
loadAgent(agentJarPath, pid);
Thread.sleep(10000); // some time to collect the data
}
private static String getPid() {
String vmName = ManagementFactory.getRuntimeMXBean().getName();
return vmName.split("@")[0];
}
private static void loadAgent(String agentJarPath, String pid) throws Exception {
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(agentJarPath);
vm.detach();
}
}
此时,我们需要在Java Agent Jar包中实现agentmain方法,并在agentmain方法中打印一条日志:
public static void agentmain(String args, Instrumentation instrumentation) {
System.out.println("Hello, This is Java Agent.");
}
Java Agent 示例应用
下面,我们讲解两个Java Agent 的示例应用。
示例一:查找Java应用程序所有类的数量
public class CountClassesMain {
private static volatile int classCount = 0;
public static void main(String[] args) throws IOException {
String agentJar = "/path/to/out/production/javaagentdemo/javaagentdemo.jar";
printAllClasses();
}
public static void printAllClasses() {
//获取到连接对象
try {
VirtualMachine vm = VirtualMachine.attach(getPid());
//安装Java Agent
vm.loadAgent("/path/to/out/production/javaagentdemo/javaagentdemo.jar");
//分析所有类
ClassDefinition[] classes = vm.getAgentProperties().getProperty("classCount");
System.out.println("发现类的数量为:" + classes.length);
} catch (Exception e) {
System.out.println("Java Agent Error");
e.printStackTrace();
}
}
private static String getPid() {
//获取当前进程PID
}
public static void agentmain(String agentArgs, Instrumentation inst) {
inst.addTransformer((loader, className, classBeingRedefined, protectionDomain, classfileBuffer) -> {
classCount++;
return classfileBuffer;
});
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
classCount++;
return classfileBuffer;
}
});
}
}
示例二:记录Java应用程序中所有的方法调用
public class MethodCallAgent {
static long startTime;
/**
* Called when the Java agent is called/started with "-javaagent" option
* on the JVM command line.
*
* @param agentArgs The arguments passed into the agent.
* @param instrumentation The agent instrumentation instance that can be used
* to transform/load classes.
*/
public static void premain(String agentArgs, Instrumentation instrumentation) {
installMethodCallHooks(instrumentation);
}
/**
* Called on-demand when a Java agent is started using the Attach API.
*
* @param agentArgs The arguments passed into the agent.
* @param instrumentation The agent instrumentation instance that can be used
* to transform/load classes.
*/
public static void agentmain(String agentArgs, Instrumentation instrumentation) {
installMethodCallHooks(instrumentation);
}
/**
* Install method call hooks on all loaded classes using Byte Buddy.
*
* @param instrumentation The agent instrumentation instance that can be used
* to transform/load classes.
*/
private static void installMethodCallHooks(Instrumentation instrumentation) {
startTime = System.currentTimeMillis();
System.out.println("Installing method call hooks...");
// Define a matcher that matches all public methods on any class
// in any package.
ElementMatcher.Junction<NamedElement> methodMatcher = isMethod()
.and(isPublic())
.and(not(isAbstract()));
// For the matched methods, wrap them inside timers.
//使用 Byte Buddy 在所有已加载的类上安装调用监视器
new AgentBuilder.Default()
.type(methodMatcher)
.transform((builder, type, classLoader, module) -> builder
.visit(Advice.to(DebugLogAdvisor.class).on(methodMatcher)))
.with(new AgentBuilder.Listener() {
@Override
public void onComplete(String s, ClassLoader classLoader, JavaModule javaModule, boolean b) {
System.out.printf("Installed method call hooks in %d ms\n",
System.currentTimeMillis() - startTime);
}
@Override
public void onError(String s, ClassLoader classLoader, JavaModule javaModule, boolean b, Throwable throwable) {
System.err.println("Error installing method call hooks: " + throwable.getMessage());
throwable.printStackTrace(System.err);
}
@Override
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule, boolean b, DynamicType dynamicType) {
}
@Override
public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule, boolean b) {
}
@Override
public void onDiscovery(String s, ClassLoader classLoader, JavaModule javaModule, boolean b) {
}
}).installOn(instrumentation);
}
/**
* Advice class that is used to wrap all matched method calls with a timer.
*/
public static class DebugLogAdvisor {
/**
* This method is invoked instead of the instrumented public void run()
* method. The advice supplies all method arguments as well as context
* information (e.g. the instance being called and the calling context).
*
* @param methodInstance The original method that was matched by the
* instrumentation.
*
* @throws Exception if something goes wrong during the monitored method
* call.
*/
@Advice.OnMethodEnter
public static void enter(@Advice.Origin Method methodInstance) throws Exception {
String methodSignature = methodInstance.toString();
System.out.printf("%s called from %s\n",
methodSignature,
getCallContext());
}
/**
* Returns the qualified class name of the class where the method
* calling the instrumented method was called from.
*
* @return The qualified class name of the calling class.
*/
private static String getCallContext() {
StackTraceElement[] stack = new Throwable().getStackTrace();
// Skip the first two stack frames (this method and the
// getCallContext() method).
StackTraceElement callingFrame = stack[2];
return callingFrame.getClassName();
}
}
}
总结:
Java Agent是相当强大的一个功能,可以对已经运行的Java应用程序进行监控和修改。Java Agent的实现原理是Java Instrumentation API,通过代理机制来对JVM进行编程控制,可以使用Javassist等工具对Java应用程序的字节码进行修改,从而有机会改变或增强Java应用程序的行为。通过补充和修正二进制字节码,我们可以非侵入性地跟踪方法的调用,对目标对象插入日志、监听等逻辑,从而实现AOP编程、性能监控等功能。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java Agent的实现原理是什么? - Python技术站