Instrumentation源码笔记

本文详细介绍了Java的Instrumentation API,包括ClassDefinition、ClassFileTransformer接口,以及如何使用java agent进行静态和动态代理。静态代理在JVM启动时生效,而动态代理则允许在运行时通过Attach API挂载。文中还提到了MANIFEST.MF文件中的关键属性,如Premain-Class和Agent-Class,并给出了测试代码示例。
摘要由CSDN通过智能技术生成

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接口实例的方法:
    1. 当以指示代理类的方式启动JVM时。 在这种情况下, Instrumentation实例被传递给代理类的premain方法。
    2. 当JVM在JVM启动后的某个时间提供启动代理的机制时。 在这种情况下, Instrumentation实例将传递给代理代码的agentmain方法。

一旦代理商获得一个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是否可以通过retransformationredefinition进行修改。 如果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

静态代理

  1. 编写premain()方法
  2. MANIFEST.MF文件中指定Premain-Class等信息
  3. 配置参数-javaagent启动打包的jar

只在main()方法执行之前运行,存在很大的局限性。另外因为java agent需要在JVM启动时添加-javaagent选项来执行,所以一般需要在虚拟机启动之时就配置,提高了组件引入成本

public static void premain(String agentArgs, Instrumentation inst); 
public static void premain(String agentArgs); 

动态代理

  1. 编写agentmain()方法
  2. MANIFEST.MF文件中指定Agent-Class等信息
  3. 使用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启动时指定代理时,此属性指定代
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值