First, what is javaagent
javaagent is a JVM "plug-in", a specially crafted .jar file, it can take advantage Instrumentation API JVM provides.
1.1, Overview
Java Agent consists of three parts: the proxy class, the meta information and proxy class loading mechanisms and .jar JVM agent, the entire contents as shown below:
1.2, the cornerstone javaagent
java.lang.instrument
By modifying the method of javaagent manner bytecode program running on the JVM services. javaagent deployed as a JAR package, specify the attributes to be loaded proxy class JAR manifest file to start the agent.
javaagent start the following ways:
-
Start by specifying parameters on the command line.
-
Start JVM started. For example, to provide a tool that can be attached to the application already running, and allows loading agents in the application has been running.
-
Packaged as an executable file with the application.
1.3, start javaagent
1.3.1, the command line to start
Command line to start the following parameters:
-javaagent:<jarpath>[=<options>]
<jarpath>
: Javaagent path, for example /opt/var/Agent-1.0.0.jar
.
<options>
: Javaagent parameters, analytical parameters responsible javaagent.
javaagent JAR manifest file must contain Premain-Class
attributes, the full path name of the agent class attribute value (package name + class name). Agent class must implement premain
methods, premain
methods, and main
methods are the same as the entry point and the application proxy. First, call the agent premain function after JVM initialization is complete, and then call the main function of the application, after premain method must return the process to start.
premain
Method signature as follows:
public static void premain(String agentArgs, Instrumentation inst)
public static void premain(String agentArgs)
JVM first attempts to call the method signature in the proxy, if the agent class does not implement the method signature, signature JVM attempts to invoke a method 2:
Acting classes can have a agentmain
function, the function call will start after the completion of the JVM. If you use the command line to start the agent,agentmain
the way will not be called.
All parameters are treated as agents by a string agentArgs
variable transmission, the agent responsible for parsing the parameter string.
If the agent because the agent class can not be loaded, the agent class does not implement premain
the method throws an exception or uncaught, JVM will exit.
javaagent start implementation is not required to provide a certain command line, if achieved support from the command line to start, implementation must support the command line by specifying the -javaagent
startup parameters. -javaagent
Can be used multiple times on the command line, start multiple agents. premain
Calling sequence and functions in the order specified in the command line is consistent, the same plurality of agents may be used <jarpath>
.
Not a strict model to define premain
the scope of work function, any main
function can do the job, such as creating a thread, the premain
function is legal.
1.3.2, the JVM startup boot
Implementation may provide a mechanism to re-start the agent after JVM startup. How to start the agent implementation-specific details, usually the application has been launched, and its main
methods have been called. If the implementation supports boot agent JVM started, the agent must meet the following criteria:
-
The manifest file contains
Agent-Class
attributes, attribute is the full name of the proxy class. -
Agent class must implement
public static agentmain
method.
agentmain signature method has the following two functions:
public static void agentmain(String agentArgs, Instrumentation inst)
public static void agentmain(String agentArgs)
JVM first attempts to call the method signature has 1, if the agent class does not implement this method, JVM attempts to invoke the method signature 2.
Acting classes can be achieved simultaneously premain
and agentmain
two methods, when the agent starts the command line, JVM calls the premain
function, when the agent started after the start JVM, JVM calls the agentmain
function, and JVM does not call the premain
function.
agentmain
Also through the transfer function parameters agentArgs
, all parameters are combined into one string, the agent responsible for the resolution parameters.
agentmain
Function must complete all the necessary Boot Agent initialization operation, when the startup is completed, agentmain
the function must return. If the agent can not be started or throw an uncaught exception, JVM will exit.
1.3.3, packaged as an executable file
If the agent is packaged into an executable JAR file, a list of executable JAR file must contain the Launcher-Agent-Class
attribute that specifies a proxy in the application before the start of the main function calls the class. JVM attempts to invoke the method on the proxy:
public static void agentmain(String agentArgs, Instrumentation inst)
If the agent class does not implement the above method, JVM following method is called.
public static void agentmain(String agentArgs)
agentArgs
Parameter must be the empty string.
agentmain
The initialization function must complete all actions the agent must start and return after startup. If the agent fails to start or throws an uncaught exception, JVM will exit.
1.3.4, loading module agent class and available proxy class / class
System class loader is responsible for loading all the classes agent JAR file, and became a member of the unnamed module system class loader. The system also typically defined class loader application contains main
class methods. All classes of proxy classes are visible to the system class loader visible, you must meet the following minimum requirements:
-
Start layer module derived classes in the package. Starting layer contains all the initial start-up mode depending on the platform module or application module.
-
Class may be defined as the system class loader.
-
All proxy classes start class loader module defined its members unnamed.
If you need to link to a proxy class layer platform (or other) module class does not start, you need to ensure that these modules are located in layers starting way to start the application. For example, the JDK implementation, --add-modules
command-line option can be used to add modules to be resolved at the start concentrated root module.
Start class loader can load proxy support classes (by appendToBootstrapClassLoaderSearch
or designated Boot-Class-Path attribute) must only link to the custom boot class loader class. We can not guarantee the boot class loader can work on all platforms.
If the custom system class loader (configured by getSystemClassLoader
the specified method system properties java.system.class.loader
), must be defined appendToSystemClassLoaderSearch
in the specified appendToClassPathForInstrumentation
method. In other words, the custom class loader system must support the addition of the agent JAR files to the system class loader mechanism within the search range.
1.4, javaagent List Properties
Attributes | Explanation | Are Required | Defaults |
---|---|---|---|
Premain-Class | Premain class method comprising | Start-dependent manner | no |
Agent-Class | The method comprising a class agentmain | Start-dependent manner | no |
Boot-Class-Path | Start class loader search path | no | no |
Can-Redefine-Classis | Can I re-define the required proxy class | no | false |
Can-Retransform-Classis | You are able to re-convert the classes needed by this agent | no | false |
Can-Set-Native-Method-Prefix | This agent can be provided if desired native method prefix | no | false |
Second, write a Java Agent
Based on the above description, we achieve a javaagent JVM all non-system classes download.
Throughout the development process includes the following three steps:
-
1) the definition of the proxy class, the implementation class download;
-
2) configuration package;
-
3) command line to start the test.
2.1, the proxy class implements
Implement premain
function
package io.ct.java.agent;
import java.lang.instrument.Instrumentation;
public class AgentApplication {
public static void premain(String arg, Instrumentation instrumentation) {
System.err.println("agent startup , args is " + arg);
// 注册我们的文件下载函数
instrumentation.addTransformer(new DumpClassesService());
}
}
Download class implements ClassFileTransformer
the interface, when the class is loaded downloaded bytecode classes:
package io.ct.java.agent;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.List;
/**
* Copyright (C), 2018-2018, open source
* FileName: DumpClassesService
*
* @author : 大哥
* Date: 2018/12/8 21:01
*/
public class DumpClassesService implements ClassFileTransformer {
private static final List<String> SYSTEM_CLASS_PREFIX = Arrays.asList("java", "sum", "jdk");
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (!isSystemClass(className)) {
System.out.println("load class " + className);
FileOutputStream fos = null;
try {
// 将类名统一命名为classNamedump.class格式
fos = new FileOutputStream(className + "dump.class");
fos.write(classfileBuffer);
fos.flush();
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
// 关闭文件输出流
if (null != fos) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return classfileBuffer;
}
/**
* 判断一个类是否为系统类
*
* @param className 类名
* @return System Class then return true,else return false
*/
private boolean isSystemClass(String className) {
// 假设系统类的类名不为NULL而且不为空
if (null == className || className.isEmpty()) {
return false;
}
for (String prefix : SYSTEM_CLASS_PREFIX) {
if (className.startsWith(prefix)) {
return true;
}
}
return false;
}
}
2.2, configuration MANIFEST.MF
MANIFEST.MF
File generated in two ways: manual and automatic configuration generation, manual configuration only need to resources
create the next file META-INF/MENIFEST.MF
file. Manually removing the outer configuration can be automatically generated using maven plug packing stage, maven plugin configuration as it follows:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>io.ct.java.agent.AgentApplication</Premain-Class>
<Agent-Class>io.ct.java.agent.AgentApplication</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
Generating a jar following format:
wherein MANIFEST.MF file follows (generating a different configuration content file is not exactly):
Manifest-Version: 1.0
Implementation-Title: agent
Premain-Class: io.ct.java.agent.AgentApplication
Implementation-Version: 0.0.1-SNAPSHOT
Built-By: chentong
Agent-Class: io.ct.java.agent.AgentApplication
Can-Redefine-Classes: true
Implementation-Vendor-Id: io.ct.java
Can-Retransform-Classes: true
Created-By: Apache Maven 3.5.4
Build-Jdk: 1.8.0_171
Implementation-URL: https://projects.spring.io/spring-boot/#/spring-bo
ot-starter-parent/agent
2.3, the command line to start Java Agent
Execute the following command to run the already compiled class Hello, can generate a file named Hellodump.class in the same directory.
java -javaagent:agent-0.0.1-SNAPSHOT.jar Hello