.
ClassDefinition
@Since:1.5
Comment:该类作为Instrumentation.redefineClasses
方法的参数块。 用来绑定需要用新的类文件字节重新定义的类
// 要重新定义的类
private final Class<?> mClass;
// 替换类文件字节
private final byte[] mClassFile;
public ClassDefinition( Class<?> theClass, byte[] theClassFile) {
if (theClass == null || theClassFile == null) {
throw new NullPointerException();
}
mClass = theClass;
mClassFile = theClassFile;
}
public Class<?> getDefinitionClass() {
return mClass;
}
public byte[] getDefinitionClassFile() {
return mClassFile;
}
ClassFileTransformer
@Since:1.5
Comment:代理提供了该接口的实现,以便转换类文件。转换发生在JVM定义类之前。
请注意,术语类文件
是在Java虚拟机规范的3.1节中定义的,指的是类文件格式的字节序列,无论它们是否驻留在文件中。
/**
该方法的实现可以转换提供的类文件并返回一个新的替换类文件。
@param loader 要转换的类的定义加载器,如果是引导加载器,可以是<code>null</code>
@param className 在Java虚拟机规范中定义的完全限定类名和接口名的内部形式的类名。例如,“java/util/List”。
@param classBeingRedefined 如果这是由重新定义或重新转换触发的,则被重新定义或重新转换的类;如果这是一个类加载,<code>null</code>
@param protectionDomain 正在定义或重定义的类的保护域
@param classfileBuffer 类文件格式的输入字节缓冲区-不能被修改
*/
byte[] transform( ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException;
Instrumentation
@Since:1.5
Comment:该类提供了用于设计Java编程语言代码所需的服务。 仪器是向方法添加字节码,用于收集工具要使用的数据。 由于这些更改纯粹是加法的,因此这些工具不会修改应用程序的状态或行为。 这种良性工具的示例包括监视代理,剖析器,覆盖分析器和事件记录器。
- 有两种获取
Instrumentation
接口实例的方法:- 当以指示代理类的方式启动JVM时。 在这种情况下,
Instrumentation
实例被传递给代理类的premain
方法。 - 当JVM在JVM启动后的某个时间提供启动代理的机制时。 在这种情况下,
Instrumentation
实例将传递给代理代码的agentmain
方法。
- 当以指示代理类的方式启动JVM时。 在这种情况下,
一旦代理商获得一个Instrumentation
实例,代理可以随时调用实例上的方法。
Method | Comment | Version |
---|---|---|
void addTransformer(ClassFileTransformer transformer, boolean canRetransform); | 注册提供的transformer 。 所有未来的类定义将被transformer 看到,除了任何已注册的transformer 所依赖的类的定义。 当类被加载时、当它们是redefined时调用transformer 。 如果canRetransform为true , 则为retransformed 。 有关变换调用的顺序,请参阅ClassFileTransformer.transform 。 如果transformer 在执行过程中引发异常,则JVM依然会调用其他已注册的transformer 。 相同的transformer 可能不止一次添加,但强烈不鼓励 - 通过创建一个新的transformer 类实例来避免这种情况。 |
1.6 |
void addTransformer(ClassFileTransformer transformer); | 注册提供的transformer 。与addTransformer(transformer, false) 相同。 |
|
boolean removeTransformer(ClassFileTransformer transformer); | 注销提供的transformer 。 未来类定义将不会显示给transformer 。 删除最新添加的transformer 匹配实例。 由于类加载的多线程性质,transformer 可以在其被移除之后接收呼叫。transformer 应该以防御的方式编写,以应对这种情况。 |
|
boolean isRetransformClassesSupported(); | 返回当前JVM配置是否支持对类进行重新传输。 转载已加载类的能力是JVM的可选功能。 如果重转换将仅被支持Can-Retransform-Classes 清单属性被设置为true 在代理JAR文件(如在所描述的package specification )和JVM支持此功能。 在单个JVM的单个实例化过程中,对此方法的多次调用将始终返回相同的答案。 |
1.6 |
void retransformClasses(Class<?>… classes) throws UnmodifiableClassException; | 重新转换提供的一组类。如果重新转换的方法具有活动堆栈帧,则这些活动帧将继续运行原始方法的字节码。 重新构建的方法将用于新的调用。 该方法不会导致任何初始化,除了在常规JVM语义下会发生。 换句话说,重新定义一个类并不会导致它的初始化器被运行。 静态变量的值将保持在调用之前。 转载类的实例不受影响。 重新转换可能会改变方法体,常量池和属性。 重新传输不能添加,删除或重命名字段或方法,更改方法的签名或更改继承。 这些限制可能在将来的版本中解除。 类文件字节不会被检查,验证和安装,直到应用转换为止,如果结果字节错误,则此方法将抛出异常。 如果此方法抛出异常,则不会重新创建任何类。 |
1.6 |
boolean isRedefineClassesSupported(); | 返回当前的JVM配置是否支持重新定义类。 重新定义已加载类的功能是JVM的可选功能。 如果重新定义将仅被支持Can-Redefine-Classes清单属性被设置为true在代理JAR文件(如在所描述的package specification )和JVM支持此功能。 在单个JVM的单个实例化过程中,对此方法的多次调用将始终返回相同的答案。 | |
void redefineClasses(ClassDefinition… definitions) throws ClassNotFoundException, UnmodifiableClassException; | 使用提供的类文件重新定义提供的一组类。 该方法用于替换类的定义,而不引用现有的类文件字节,就像从源进行重新编译以进行修复和继续调试时一样。 在现有的类文件字节要转换的地方(例如,在字节码仪器中)应该使用retransformClasses 。 重新定义可能会改变方法体,常量池和属性。 重定义不能添加,删除或重命名字段或方法,更改方法的签名或更改继承。 这些限制可能在将来的版本中解除。 类文件字节不会被检查,验证和安装,直到应用转换为止,如果结果字节错误,则此方法将抛出异常。 如果此方法抛出异常,则不会重新定义任何类。 |
|
boolean isModifiableClass(Class<?> theClass); | 确定Class 是否可以通过retransformation 或redefinition 进行修改。 如果Class 是可修改的,那么此方法返回true 。 如果类不可修改,则此方法返回false 。 |
1.6 |
Class[] getAllLoadedClasses(); | 返回当前由JVM加载的所有类的数组。 | |
Class[] getInitiatedClasses(ClassLoader loader); | 返回loader 是起始加载程序的所有类的数组。 如果提供的加载程序是null ,则返回由引导类加载器启动的类。 |
|
long getObjectSize(Object objectToSize); | 返回指定对象所消耗的存储量的实现特定近似值。 结果可能包括一些或全部对象的开销,因此对于实现之间的比较而言不是有用的。 在JVM的单次调用期间,估计可能会发生变化。 | |
void appendToBootstrapClassLoaderSearch(JarFile jarfile); | 指定由引导类装入器定义的带有插装类的JAR文件。 | 1.6 |
void appendToSystemClassLoaderSearch(JarFile jarfile); | 指定具有由系统类加载器定义的检测类的JAR文件。 当用于委派的系统类加载器(getSystemClassLoader )不成功地搜索类时,也将搜索JarFile 中的条目。 |
1.6 |
boolean isNativeMethodPrefixSupported(); | 返回当前JVM配置是否支持setting a native method prefix 。 设置本机方法前缀的功能是JVM的可选功能。 设置本机方法前缀,如果将仅被支持Can-Set-Native-Method-Prefix 清单属性被设置为true 在代理JAR文件和JVM支持此功能。 在单个JVM的单个实例化过程中,对此方法的多次调用将始终返回相同的答案 |
1.6 |
void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix); | 此方法通过允许使用应用于名称的前缀进行重试来修改本机方法解析的故障处理。 当与ClassFileTransformer 一起使用时,它可以对本地方法进行检测。 |
1.6 |
需要额外了解的技术:javassist
java agent
静态代理
- 编写premain()方法
- MANIFEST.MF文件中指定Premain-Class等信息
- 配置参数-javaagent启动打包的jar
只在main()方法执行之前运行,存在很大的局限性。另外因为java agent需要在JVM启动时添加-javaagent选项来执行,所以一般需要在虚拟机启动之时就配置,提高了组件引入成本
public static void premain(String agentArgs, Instrumentation inst);
public static void premain(String agentArgs);
动态代理
- 编写agentmain()方法
- MANIFEST.MF文件中指定Agent-Class等信息
- 使用Attach API挂载打包的jar的目标进程
可以在JVM启动后任意时刻通过Attach API动态加载,我们常用的jstack、jmap、arthas等工具都是通过动态加载Java Agent实现的
public static void agentmain(String agentArgs, Instrumentation inst);
public static void agentmain(String agentArgs);
MANIFEST属性
- Premain-Class:当在JVM启动时指定代理时,此属性指定代