在上一篇中我們已經(jīng)介紹了java agent的相關(guān)概念和思想,給出了premain方式的實現(xiàn)代碼。本篇主要是實現(xiàn)了attach方式,不同之處主要如下:
- premain是靜態(tài)修改,在類加載之前修改; attach是動態(tài)修改,在類加載后修改
- 要使premain生效重啟應(yīng)用,而attach不重啟應(yīng)用即可修改字節(jié)碼并讓其重新加載
可以看到attach的方式更加強大,其核心原理首先是找到相關(guān)的進程id, 然后根據(jù)進程id去動態(tài)修改相關(guān)字節(jié)碼,具體的修改方式和premain無差,下面就直接給出詳細(xì)實現(xiàn)。
項目結(jié)構(gòu)(此處為了方便把主程序和Agent程序放在一起, 實際生產(chǎn)中肯定是分開的):
agentdemo
├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── hebh
│ └── demo
│ ├── agent
│ │ ├── MyInstrumentationAgent.java
│ │ └── MyTransformer.java
│ └── application
│ ├── AgentLoader.java
│ ├── Launcher.java
│ ├── MyApplication.java
│ └── Runner.java
└── resources
├── META-INF
│ └── MANIFEST.MF
└── log4j2.xml
先看測試結(jié)果:
-
打成jar包
mvn clean package -
運行主程序
java -jar target/myAgent-jar-with-dependencies.jar -
運行agent程序, 注意帶上系統(tǒng)的lib目錄
java -Djava.ext.dirs=${JAVA_HOME}/lib -jar target/myAgent-jar-with-dependencies.jar LoadAgent如下圖所示,可以看到首先找到主程序的進程id為4477,然后再attach上去

image-20190126202600306
然后此時再看主程序的運行日志, 可以看到在attach后動態(tài)增加的字節(jié)碼生效了,實現(xiàn)了方法耗時監(jiān)控:

image-20190126203002824
詳細(xì)代碼:
MANIFEST.MF
Main-Class: com.hebh.demo.application.Launcher
Agent-Class: com.hebh.demo.agent.MyInstrumentationAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hebh</groupId>
<artifactId>agent-demo</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>A custom project using myfaces</name>
<url>http://www.myorganization.org</url>
<build>
<finalName>myAgent</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<!--避免MANIFEST.MF被覆蓋-->
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
</archive>
<descriptorRefs>
<!--打包時加入依賴-->
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- Project dependencies -->
<dependencies>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.24.1-GA</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.1</version>
</dependency>
</dependencies>
</project>
主程序和Agent程序的路由:
public class Launcher {
public static void main(String[] args) throws Exception {
if(args != null && args.length > 0 && "LoadAgent".equals(args[0])) {
new AgentLoader().run();
}else{
new MyApplication().run();
}
}
}
主程序部分:
public class MyApplication {
private static Logger logger = LogManager.getLogger(MyApplication.class);
public static void run() throws Exception {
logger.info("[Application] Starting My application");
Runner runner = new Runner();
for(;;){
runner.run();
}
}
}
public class Runner {
private static final Logger logger = LogManager.getLogger(Runner.class);
public void run() throws InterruptedException{
long sleep = (long)(Math.random() * 1000 + 200);
Thread.sleep(sleep);
logger.info("run in [{}] millis!", sleep);
}
}
Agent部分:
public class AgentLoader {
private static Logger logger = LogManager.getLogger(AgentLoader.class);
public static void run() {
//指定jar路徑
String agentFilePath = "/Users/baohuahe/demos/agentdemo/target/myAgent-jar-with-dependencies.jar";
//需要attach的進程標(biāo)識
String applicationName = "myAgent";
//查到需要監(jiān)控的進程
Optional<String> jvmProcessOpt = Optional.ofNullable(VirtualMachine.list()
.stream()
.filter(jvm -> {
logger.info("jvm:{}", jvm.displayName());
return jvm.displayName().contains(applicationName);
})
.findFirst().get().id());
if(!jvmProcessOpt.isPresent()) {
logger.error("Target Application not found");
return;
}
File agentFile = new File(agentFilePath);
try {
String jvmPid = jvmProcessOpt.get();
logger.info("Attaching to target JVM with PID: " + jvmPid);
VirtualMachine jvm = VirtualMachine.attach(jvmPid);
jvm.loadAgent(agentFile.getAbsolutePath());
jvm.detach();
logger.info("Attached to target JVM and loaded Java agent successfully");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public class MyInstrumentationAgent {
private static Logger logger = LogManager.getLogger(MyInstrumentationAgent.class);
public static void agentmain(String agentArgs, Instrumentation inst) {
logger.info("[Agent] In agentmain method");
//需要監(jiān)控的類
String className = "com.hebh.demo.application.Runner";
transformClass(className, inst);
}
private static void transformClass(String className, Instrumentation instrumentation) {
Class<?> targetCls = null;
ClassLoader targetClassLoader = null;
// see if we can get the class using forName
try {
targetCls = Class.forName(className);
targetClassLoader = targetCls.getClassLoader();
transform(targetCls, targetClassLoader, instrumentation);
return;
} catch (Exception ex) {
logger.error("Class [{}] not found with Class.forName");
}
// otherwise iterate all loaded classes and find what we want
for(Class<?> clazz: instrumentation.getAllLoadedClasses()) {
if(clazz.getName().equals(className)) {
targetCls = clazz;
targetClassLoader = targetCls.getClassLoader();
transform(targetCls, targetClassLoader, instrumentation);
return;
}
}
throw new RuntimeException("Failed to find class [" + className + "]");
}
private static void transform(Class<?> clazz, ClassLoader classLoader, Instrumentation instrumentation) {
MyTransformer dt = new MyTransformer(clazz.getName(), classLoader);
instrumentation.addTransformer(dt, true);
try {
instrumentation.retransformClasses(clazz);
} catch (Exception ex) {
throw new RuntimeException("Transform failed for class: [" + clazz.getName() + "]", ex);
}
}
}
public class MyTransformer implements ClassFileTransformer {
private static Logger logger = LogManager.getLogger(MyTransformer.class);
//需要監(jiān)控的方法
private static final String WITHDRAW_MONEY_METHOD = "run";
/** The internal form class name of the class to transform */
private String targetClassName;
/** The class loader of the class we want to transform */
private ClassLoader targetClassLoader;
public MyTransformer(String targetClassName, ClassLoader targetClassLoader) {
this.targetClassName = targetClassName;
this.targetClassLoader = targetClassLoader;
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] byteCode = classfileBuffer;
String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/"); //replace . with /
if (!className.equals(finalTargetClassName)) {
return byteCode;
}
if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
logger.info("[Agent] Transforming class" + className);
try {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get(targetClassName);
CtMethod m = cc.getDeclaredMethod(WITHDRAW_MONEY_METHOD);
// 開始時間
m.addLocalVariable("startTime", CtClass.longType);
m.insertBefore("startTime = System.currentTimeMillis();");
StringBuilder endBlock = new StringBuilder();
// 結(jié)束時間
m.addLocalVariable("endTime", CtClass.longType);
endBlock.append("endTime = System.currentTimeMillis();");
// 時間差
m.addLocalVariable("opTime", CtClass.longType);
endBlock.append("opTime = endTime-startTime;");
// 打印方法耗時
endBlock.append("logger.info(\"completed in:\" + opTime + \" millis!\");");
m.insertAfter(endBlock.toString());
byteCode = cc.toBytecode();
cc.detach();
} catch (Exception e) {
logger.error("Exception", e);
}
}
return byteCode;
}
}