天天看点

Instrumentation类方法简单介绍前言一、Instrumentation类二、ClassFileTransformer类总结

文章目录

  • 前言
  • 一、Instrumentation类
    • 1.addTransformer(ClassFileTransformer transformer, boolean canRetransform)
    • 2.addTransformer(ClassFileTransformer transformer)
    • 3.removeTransformer(ClassFileTransformer transformer)
    • 4.isRetransformClassesSupported()
    • 5.retransformClasses(Class… classes) throws UnmodifiableClassException
    • 6.isRedefineClassesSupported()
    • 7.redefineClasses(ClassDefinition… definitions) throws ClassNotFoundException, UnmodifiableClassException
    • 8.isModifiableClass(Class theClass)
    • 9.getAllLoadedClasses()
    • 10.getInitiatedClasses(ClassLoader loader)
    • 11.getObjectSize(Object objectToSize)
    • 12.appendToBootstrapClassLoaderSearch(JarFile jarfile)
    • 13.appendToSystemClassLoaderSearch(JarFile jarfile)
    • 14.isNativeMethodPrefixSupported()
    • 15.setNativeMethodPrefix(ClassFileTransformer transformer, String prefix)
  • 二、ClassFileTransformer类
    • 1.transform
  • 总结

前言

Instrumentation主要是提供Java代码增强功能。例如为了提供收集数据的功能,可以通过字节码增强技术,对类的方法进行增强。由于代码是添加的,所以他不会修改原来的状态。通过这个API,我们可以开发各种工具,例如监控的客户端,profiles,代码覆盖率分析工具以及事件的日志埋点工具。

使用方式:

一是在java启动的时候添加启动参数javaagent将你的增强包加载进来,会在permain里把Instrumentation传过来

二是使用VirtualMachine的API来进行。

具体的做法是调用VirtualMachine.attach(java pid),会在agentmain方法里把Instrumentation实例传过来

提示:以下是本篇文章介绍其中的方法

一、Instrumentation类

1.addTransformer(ClassFileTransformer transformer, boolean canRetransform)

代码如下(示例):

函数的作用是注册一段代码增强逻辑,它能对所有已定义的类生效(除了通过依赖的transformer进行定义的类)。

当一个类被加载的时候,或者是redefineClasses方法被调用的时候,或者是retransformClasses方法被调用的时候(前提是canRetransform的参数是true),这个函数里注册的transformer就被调用了。

至于transformer之间调用的顺序,则是由添加的先后顺序来决定的(假设有多个agent对同一个类的方法进行了增强,他们是按照先后顺序来执行的)可以想象一下管道命令,是一样的。

异常问题,值得注意的是,如果这个方法在执行的时候发生了异常,jvm不会中断,他会继续进行下一个transformer的执行。

重复add的问题,同一个transformer可能会被add多次,这个是不被推荐的,add多次最好new新的对象进行。

当传入的transformer是null的时候,这个方法会抛出NullPointException的异常,当canRetransform被设为true,而JVM虚拟机被设置为不可被reTransfrom的时候(参见isRetransformClassesSupported方法),会抛出UnsupportedOperationException异常。

2.addTransformer(ClassFileTransformer transformer)

代码如下(示例):

等同于addTranformer(transformer,false);

3.removeTransformer(ClassFileTransformer transformer)

代码如下(示例):

移除已注册的transformer。未来被定义的类将不会被应用这个transformer。由于类加载的多线程语义性,可能被移除的transformer还会依然生效,所以业务方在编写的时候要处理好,做好防御性编程。

4.isRetransformClassesSupported()

代码如下(示例):

返回当前的JVM配置是否支持类的reTransform。这个取决于你的javaagent里manifest的配置项的Can-Retransform-Classes配置

5.retransformClasses(Class… classes) throws UnmodifiableClassException

代码如下(示例):

retransformClasses(Class… classes) throws UnmodifiableClassException
           

函数的用途是对传入的classes执行transform,一般用于agentmain的方式。因为agentmain的方式是在class已经被加载完了之后attch到jvm上的,这个时候只有通过这种方式来修改原来的类的行为。

所以在add的时候,canTransform设置为false的将会被忽略,只有设置为true的才会被执行。如果你的jvm的Can-Retransform-Classes被设置为false,会抛出异常。同时如果你的类有问题,可能会抛出ClassFormatError,NoClassDefFoundError,ClassCircularityError,LinkageError异常,如果传入的是null,会报NullPointerException

6.isRedefineClassesSupported()

代码如下(示例):

同isRetransformClassesSupported,在manifest里配置的

7.redefineClasses(ClassDefinition… definitions) throws ClassNotFoundException, UnmodifiableClassException

代码如下(示例):

redefineClasses(ClassDefinition… definitions) throws  ClassNotFoundException, UnmodifiableClassException
           

和retransformClasses非常的相似。

retransformClasses是fix-and-continues,而redefined则是直接替换掉了。尤其是有多个agent同时工作的时候,更加推荐retransform。

8.isModifiableClass(Class theClass)

代码如下(示例):

这个类是否能够被transform,如果可以则返回true

9.getAllLoadedClasses()

代码如下(示例):

获取所有被JVM加载的类,这个是诊断的好帮手

10.getInitiatedClasses(ClassLoader loader)

代码如下(示例):

获取所有被指定的classloader加载的类,如果传入null则返回由BootstrapClassl返回的类

11.getObjectSize(Object objectToSize)

代码如下(示例):

获取一个对象消耗的空间大小

12.appendToBootstrapClassLoaderSearch(JarFile jarfile)

代码如下(示例):

将指定的jar包加载给BootstrapClassLoader,可以被执行多次从而加载多个jar包。查找类的时候会先去BootStrapLoader里去找,如果找不到就到这个jar包里面去找。

需要小心处理的是,包名不要重复。例如,在ClassLoader L里加载了一个类C,他有一个私有类是C$1,如果你的jar包里正好也有个类C$1,他会先去BootstrapClassLoader里去找,发现找到了,然后去load,接着发现没有权限,就会直接报出IllegalAccessError错误。

13.appendToSystemClassLoaderSearch(JarFile jarfile)

代码如下(示例):

特性和appendToBootstrapClassLoader类似,不过区别是这个是用于SystemClassLoader的类的查找的(回忆一下双亲委派机制)。

14.isNativeMethodPrefixSupported()

代码如下(示例):

是否设置了本地方法的拦截,由manifest设置的。Can-Set-Native-Method-Prefix属性,具体set native method prefix的用途参考nativeMethodPrefix()

15.setNativeMethodPrefix(ClassFileTransformer transformer, String prefix)

代码如下(示例):

对本地方法进行代码增强。但是由于本地方法是没有字节码的,所以他的办法是重新定义一个同名的函数(原来native method)的名称,然后将原来的本地方法使用prefix进行重命名。

举例,以前有一个本地方法 native boolean foo(String x),他的做法是先将native的方法重命名,例如改成

native boolean wapped_foo(String x), 然后再定义一个方法叫

boolean foo(String x) { 
	—你的代码— ; 
	prefix_foo(x)
}
           

为了防止重复,所以你的prefix最好考虑到方法重复的情况。

正常情况下,按照上面顺序执行,但是如果有问题的时候他的执行顺序分别是

method(foo) -> nativeImplementation(foo)

method(wrapped_foo) -> nativeImplementation(foo)

method(wrapped_foo) -> nativeImplementation(wrapped_foo)

method(wrapped_foo) -> nativeImplementation(foo)

二、ClassFileTransformer类

用来让用户来实现代码增强逻辑的接口。他只有一个方法,方法的参数是原来的类的字节码以及这个类的classloader对象,返回值则是被增强之后的类的字节码。所以你的代码是通过已有的类信息,植入代码之后,形成新的代码(类的字节码)。

1.transform

代码如下(示例):

transform(ClassLoader,ClassName,classBeingRedefined,protectionDomain,classfileBuffer)
           

这里的来源有两个,分别是Instrumentation的两个addInstrumentation()方法。

这个函数被调用的时机有如下几种情况:

  1. 这个类第一次被加载,当ClassLoader的defineClass()方法(这个方法是classloader将字节码解析成类并且放入metaspace的过程)被调用的时候,这个方法会被call
  2. Instrumentation#redefineClasses被调用了这个方法的时候,这个方法也会被调用
  3. Instrumentation#retransformClasses当这个方法被调用的时候,这个方法也会被调用

    他们的执行是按照addInstrumentation的顺序来执行的。而reTransformClasses和redefineClasses被执行的时候,他们修改的字节码是在之前已经被增强的基础上进行的。

例如原始方法是foo(String x), 然后在类加载的时候进行了一些增强,插入了一段代码,那么在下次retransformClasses执行,这个方法被调用的时候,增加的字节码是在上一次的基础上进行的。换句话说,classfileBuffer永远都是最后增强执行完成之后的版本。

关于异常和返回值。如果返回值是null,代表这次调用什么都没做,没有任何增强代码加入。他的效果和抛出异常是一样的,如果在执行这个函数的过程中抛出了异常,则代表本次调用什么都不做,和返回null值的效果是一样的。

总结

本文仅仅简单介绍了Instrumentation类的方法,具体Instrumentation的使用还需要结合Javassit开源的分析、编辑和创建Java字节码的类库,后续会继续介绍

参考文档