類(lèi)加載的步驟:1.加載 2.校驗(yàn) 3.準(zhǔn)備 4.解析(不固定:對(duì)于動(dòng)態(tài)調(diào)用可能在初始化后解析,例如多態(tài)的實(shí)現(xiàn),java8的lambda語(yǔ)法) 5.初始化 6.使用 7.卸載
Instrumentation原理:在類(lèi)加載器加載過(guò)程中對(duì)class文件流進(jìn)行攔截替換,
Instrumentation提供了獲取對(duì)象大小的方法:getObjectSize
META-INF/MANIFEST.MF文件內(nèi)容
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 對(duì)于類(lèi) :java/lang/Void
//className 對(duì)于內(nèi)部類(lèi) :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;
}
}
測(cè)試類(lèi)
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對(duì)象的大小
long objectSize = instrumentation.getObjectSize(new Test());
System.out.println("Object Test size is "+objectSize +" Byte");
}
}
}
運(yùn)行Test時(shí):java -javaagent:agent-1.0-SNAPSHOT.jar=/test/conf.properties Test
/test/conf.properties配置類(lèi)似如下:(注意String類(lèi)無(wú)法覆蓋由于系統(tǒng)在使用Instrumentation前就已經(jīng)加載了String類(lèi),日志中:[premain][LoadedClasses]的類(lèi)提前加載到內(nèi)存都無(wú)法進(jìn)行覆蓋)
java/lang/String=/opt/class/String.class #只作為格式參考
對(duì)于提前加載的類(lèi)可以使用Instrumentation#redefineClasses進(jìn)行修改,但是有他的局限性
1.不允許新增加field/method 2.正在跑的函數(shù),沒(méi)有退出不能生效