类加载的步骤:1.加载 2.校验 3.准备 4.解析(不固定:对于动态调用可能在初始化后解析,例如多态的实现,java8的lambda语法) 5.初始化 6.使用 7.卸载
Instrumentation原理:在类加载器加载过程中对class文件流进行拦截替换,
Instrumentation提供了获取对象大小的方法:getObjectSize
META-INF/MANIFEST.MF文件内容
Manifest-Version: 1.0
Premain-Class: com.paulzhangcc.InstrumentationHolder
Can-Redefine-Classes: true
Can-Retransform-Classes: true
pom.xml
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.paulzhangcc</groupId>
<artifactId>agent</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>
src/main/resources/META-INF/MANIFEST.MF
</manifestFile>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
package com.paulzhangcc;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.Properties;
/**
* @author paul
* @description
* @date 2018/8/7
*/
public class InstrumentationHolder {
public static final String default_config_path = "/opt/java-agent.properties";
public static Instrumentation instrumentation = null;
public static void premain(String agentArgs, Instrumentation instrumentationTemp) {
instrumentation = instrumentationTemp;
System.out.println("[premain][init ]:agentArgs=" + agentArgs + ",instrumentationName="+instrumentationTemp.getClass().getName());
Class[] allLoadedClasses = instrumentationTemp.getAllLoadedClasses();
for (Class _class:allLoadedClasses){
System.out.println("[premain][LoadedClasses]:className="+_class.getName());
}
Properties properties = new Properties();
try {
if (agentArgs != null && agentArgs.length() != 0) {
System.out.println("[premain][read ]:config_path="+agentArgs);
properties.load(new FileInputStream(agentArgs));
} else {
System.out.println("[premain][read ]:default_config_path="+default_config_path);
properties.load(new FileInputStream(default_config_path));
}
} catch (Exception e) {
if (e instanceof FileNotFoundException) {
try {
properties.load(new FileInputStream(default_config_path));
} catch (IOException e1) {
System.out.println("[premain][error]:how to use:java -javaagent:{1}={2} {1} is agent jar ,{2} is conf properties , default {2} is /opt/java-agent.properties");
System.out.println("[premain][error]:/opt/java-agent.properties for example sun/security/util/HostnameChecker=C:/Users/paul/Desktop/HostnameChecker.class");
}
} else {
e.printStackTrace();
}
}
if (properties.isEmpty()){
System.out.println("[premain][info ]:config properties is empty,so do not transform Class");
return;
}
instrumentationTemp.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
//className 对于类 :java/lang/Void
//className 对于内部类 :java/lang/Class$MethodArray
String property = properties.getProperty(className);
if (property != null) {
byte[] fileBytes = getFileBytes(property);
if (fileBytes != null) {
System.out.println("[premain][replace]:className=" + className + ",fileName="+property);
return fileBytes;
}
}
return null;
}
}, true);
}
public static byte[] getFileBytes(String fileName) {
try {
File file = new File(fileName);
if (!file.exists()) {
return null;
}
long fileSize = file.length();
FileInputStream fi = new FileInputStream(file);
byte[] buffer = new byte[(int) fileSize];
int offset = 0;
int numRead = 0;
while (offset < buffer.length
&& (numRead = fi.read(buffer, offset, buffer.length - offset)) >= 0) {
offset += numRead;
}
if (offset != buffer.length) {
return null;
}
fi.close();
return buffer;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
测试类
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Field;
/**
* @author paul
* @description
* @date 2018/11/12
*/
public class Test {
public static Instrumentation getInstrumentation(){
try {
Class<?> aClass = Class.forName("com.paulzhangcc.InstrumentationHolder");
Field instrumentation = aClass.getField("instrumentation");
return (Instrumentation) instrumentation.get(null);
}catch (Exception e){
}
return null;
}
public static void main(String[] args) throws Exception {
Instrumentation instrumentation = getInstrumentation();
if (instrumentation != null){
//查看Test对象的大小
long objectSize = instrumentation.getObjectSize(new Test());
System.out.println("Object Test size is "+objectSize +" Byte");
}
}
}
运行Test时:java -javaagent:agent-1.0-SNAPSHOT.jar=/test/conf.properties Test
/test/conf.properties配置类似如下:(注意String类无法覆盖由于系统在使用Instrumentation前就已经加载了String类,日志中:[premain][LoadedClasses]的类提前加载到内存都无法进行覆盖)
java/lang/String=/opt/class/String.class #只作为格式参考
对于提前加载的类可以使用Instrumentation#redefineClasses进行修改,但是有他的局限性
1.不允许新增加field/method 2.正在跑的函数,没有退出不能生效
网友评论