JavaSe:Instrumentation

  • Instrumentation
  • 类加载过程
  • Instrumentation与Transformer
    • Instrumentation与Transformer的关系
    • Instrumentation接口简介
    • 触发字节码转换的途径
    • Transformer分类
    • Transformer调度过程
    • Transformer如何实现
    • redefineClasses、retransformClasses
  • 注意事项

Instrumentation

Instrumentation是JDK提供的用于支撑字节码修改的服务。在某些情况下,需要对Java程序进行性能优化、监控、分析、日志记录等工作时,就需要通过字节码修改来完成了。常见的字节码修改工具有:BCEL、ASM、CGLib、Javassist。Instrumentation并不是一个字节码修改工具,它是用来为字节码修改工具提供入口的,也就是字节码修改工具要通过Instrumentation才能接入到一个Java进程里,才能进行字节码修改操作。

类加载过程

如果去网上搜索类加载过程,能搜到很多,那些文章大致可以分为两类:

1: 类从加载到虚拟机到卸载,它的整个生命周期包括:加载(Loading),验证(Validation),准备(Preparation),解析(Resolution),初始化(Initialization),使用(Using)和卸载(Unloading)。其中,验证、准备和解析部分被称为连接(Linking)。然后再说一下每个过程做的什么。但是这些文章对于理解Instrumentation ,帮助并不大。

2:第二类是讲述ClassLoader机制的、但是讲述的范围无非就是delegate加载,或者是子类优先加载两种方式罢了。

总之、很少有关注类加载时要注意什么、类加载后又做了什么。而这些不被关注的地方恰恰是理解Instrumentation所需要的。

ClassLoader.loadClass()的过程是:

ClassLoader.loadClass()            ——>ClassLoader.findClass()                          ——>defineClass(byte[])
           ——>resolveClass(Class)

当一个ClassLoader加载一个类时,在找到find到.class文件后,会调用defineClass()将字节码转换成Class对象。然后再通过resolveClass(Class)进行linking(连接)操作,连接完毕就可以使用了。

在defineClass()中,一般是分为3个步骤进行:

·设置protectionDomain

·通过本地代码(defineClass1)来进行类定义。

·后置处理。

Instrumentation与Transformer

Instrumentation与Transformer的关系

Transformer是字节码转换的接口,Instrumentation是管理Transformer、调度Transformer进行字节码转换的门面。两者关系如下:

TransformerManager用于管理、调度Transformer的,但是这个类是不可见的(不能直接在代码里使用的)。Instrumentation是一个门面,在程序中,直接使用的是Instrumentation的实例。

Instrumentation接口简介

当执行Instrumentation的addTransformer、removeTransformer方法时,最终是调用了TransformerManager的addTransformer、removeTransformer。以此来管理Transformer。

Instrumentation的retransformClasses、redefineClasses是用于通知TransformerManager调度字节码转换的。通常在执行这两个方法之前,要先判断是否支持相应的操作,即要先调用isRedefineClassesSupported、isRetransformClassesSupporte。这是因为不同的JVM对JAVA标准支持不同,可能有些JDK不支持这些操作。即便是Oralce(Sun)的JDK,低版本的也是不支持这些操作的。

另外,使用Instrumentation,还可以动态的向BootstrapClassLoader、SystemClassLoader的搜索路径下添加Jar、classes等。

触发字节码转换的途径

上一小节中说了,通过Instrumentation的retransformClasses、redefineClasses通知TransformerManager调度Transformer来进行字节码转换。其实还有一种方式,也可以通知字节码转换。

在类加载过程这一小节的最后提到了ClassLoader.defineClass1()这个方法用于进行类的定义。这个方法是在本地代码中实现的,但这个过程也会通知TransformerManager调度Transformer来进行字节码转换。

所以,共有三种方式来通知Transformer来进行字节码转换。而这三种方式,也有另外一种叫法:加载类时、重定义类时、重转换类时。

Transformer分类

Transformer可以分为两类:可重转换的Transformer、不可重转换的Transformer。任何一个Transformer都可以用于加载类时、重定义类时进行转换。如果是可重转换的Transformer,也可以在重转换时进行转换。

注册Transformer时(通过Instrumentation.addTransformer(ClassFileTransformer,  canRetransform)来注册),将第二个参数设为false就是不可重转换,设置为true就是可重转换的转换器。

Transformer的调度过程

当存在多个转换器时,转换将由transform调用链组成。也就是说,一个transform调用返回的byte数组将成为下一个调用的输入(通过classfileBuffer参数)。

转换将按以下顺序进行:

·不可重转换转换器

·不可重转换本地(native)转换器

·可重转换转换器

·可重转换本地(native)转换器

对于重转换,不会调用不可重转换转换器,而是重用前一个转换的结果。对于所有其他情况,调用此方法。在每个这种调用组中,转换器将按照注册的顺序调用。本机转换器由 Java 虚拟机 Tool 接口中的 ClassFileLoadHook 事件提供。

Transformer如何实现

ClassFileTransformer接口中只有一个方法:

它是用于将已有的字节码classfileBuffer转换成新的字节码。所以在这里面,可以使用字节码工具来修改。

参数说明:

loader :加载该类的类加载器。如果是boostrapClassLoader,则为null。

className:类完全限定名,用/分隔。例如(java/util/List)

classBeingRedeined 如果是null,说明这次转换是在类加载时发起的。

protectionDomain:要定义或者重定义的类的保护域。

classfileBuffer:要被修改的字节码。(这些字节码可能是第一次加载、也可能是第1..n次被转换)。

返回值:

如果不做任何转换,则要返回null。

如果转换器抛出异常(未捕获的异常),后续转换器仍然将被调用并加载,仍然将尝试重定义或重转换。因此,抛出异常与返回 null 的效果相同。

redefineClasses与retransformClasses

redefineClasses

·使用提供的类文件重定义提供的类集。

·此方法用于替代类的定义,而不引用现有的类文件字节,这与 fix-and-continue 调试过程中重新编译源代码时所做的一样。需要转换现有类文件字节的地方(例如,字节码检测中)应该使用retransformClasses

·此方法在一个集合上操作,以便允许同时对多个类进行相互依赖的更改(重定义类 A 要求重定义类 B)。

·如果重定义的方法有活动的堆栈帧,那么这些活动的帧将继续运行原方法的字节码。将在新的调用上使用此重定义的方法。

·此方法不会引起任何初始化操作,JVM 惯例语义下发生的初始化除外。换句话说,重定义一个类不会引起其初始化方法的运行。静态变量的值将与调用之前的值一样。

·重定义类的实例不受影响。

·重定义可能会更改方法体、常量池和属性。重定义不得添加、移除、重命名字段或方法;不得更改方法签名、继承关系。在以后的版本中,可能会取消这些限制。在应用转换之前,类文件字节不会被检查、验证和安装。如果结果字节错误,此方法将抛出异常。

·如果此方法抛出异常,则不会重定义任何类。

retransformClasses

·此方法在一个集合上操作,以便允许同时对多个类进行相互依赖的更改(重转换类 A 要求重转换类 B)。

·如果重转换的方法有活动的堆栈帧,那么这些活动的帧将继续运行原方法的字节码。重转换的方法将用于新的调用。

·此方法不会引起任何初始化操作,JVM 惯例语义下发生的初始化除外。换句话说,重定义一个类不会引起其初始化方法的运行。静态变量的值将与调用之前的值一样。

·重转换类的实例不受影响。

·重转换可能会更改方法体、常量池和属性。重转换不得添加、移除、重命名字段或方法;不得更改方法签名、继承关系。在以后的版本中,可能会取消这些限制。在应用转换之前,类文件字节不会被检查、验证和安装。如果结果字节错误,此方法将抛出异常。

·如果此方法抛出异常,则不会重转换任何类。

不论是redefineClasses,还是retransformClasses,都有这样的:

1、如果重转换(或者重定义)的方法有活动的堆栈帧,那么这些活动的帧将继续运行原方法的字节码。重转换(或者重定义)的方法将用于新的调用。这一条说明了,在重转换(或者重定义)时,一个类的定义可能会存在多个版本。

在JDK 8下,有方法可以是default 的,故而可能会引起ICCE(IncompatibleClassChangeError),这点在IBM JDK8下发现,在Oralce JDK8下没有发现。

2、重转换(或者重定义)可能会更改方法体、常量池和属性。重转换不得添加、移除、重命名字段或方法;不得更改方法签名、继承关系。

但如果这次转换是由类加载引起的,是可以不遵循这条规则的。

两者差异:

redefineClasses、retransformClasses 基本是相同的,主要 在不同点是retransformClasses可以跨多个agent的。也就是由retransformClasses 发起的转换,会调用每个agent中添加的transformer。

注意事项

为Agent开启redefine功能:
在javaagent的MANIFEST.MF里设置`Can-Redefine-Classes:true`
为Agent开启retransform功能:
在javaagent的MANIFEST.MF文件里定义了`Can-Retransform-Classes:true`

时间: 2024-10-11 17:12:55

JavaSe:Instrumentation的相关文章

与大家分享robotium一个小问题。Test run failed:Instrumentation run failed due to 'java.lang.ClassNotFoundException'

今天和大家分享robotium一个小问题. 我们在运行自已经搭好的框架时,有可能会出现一个找不到类的错误(如上图所示). 问题是重签名工具给出的activity有误,这时我们可以用Appt命令查看重签名后的apk的activity,这个才是正确的. 1.进入到SDK安装目录下有Appt.exe程序的文件夹 2.输入命令aapt dump badging D:\qq.unar_debug.apk.(注:命令+apk的存放路径) 3.可查看包名.版本.开发用的sdk的版本 4.查看activity

JavaSe:ThreadLocal

JDK中有一个ThreadLocal类,使用很方便,但是却很容易出现问题.究其原因, 就是对ThreadLocal理解不到位.最近项目中,出现了内存泄漏的问题.其中就有同事在使用ThreadLocal时,没有用好.所以特写下此文. ThreadLocal的设计 ThreadLocalMap.ThreadLocal说明 使用ThreadLocal后的内存模型 如何正确的使用ThreadLocal 错误的使用ThreadLocal会造成内存泄漏 ThreadLocal设计 ThreadLocal的类

JavaSE:异常总结(Exception)

整体结构:        java.lang.Throwable               java.lang.Error               java.lang.Exception                      java.lang.RuntimeException 编译时异常为受检异常(checked) 1.异常:程序在执行过程中发生的不正常情况,程序员可以捕获处理 错误:不期望被用户捕获的异常,如计算机硬件的损坏,内存溢出. 2.编译时异常:.java源文件在执行.ja

JavaSe:Comparator

今天,公司里有一个萌萌的妹子问我java 中的comparator是怎么回事.参数分别是什么,返回值又是什么,为此,我写了一个简单的程序告诉了她: public static void main (String[] args){ List<String> list = new ArrayList<String>(Arrays.asList(new String[]{"1", "a", "222", "221&qu

JavaSe:Properties文件格式

Properties文件格式说明 Properties继承自Hashtable,是由一组key-value的集合. 在Java中,常用properties文件作为配置文件.它的格式是什么样的呢? 下图是一个用于展示格式的properties文件 下面是测试结果: properties文件的书写要求总结: 1.注释内容由 # 或者! 开头 2.key,value之间用 = 或者 : 分隔.一行中既有=也有:时,第一个(或者=或者:)将作为key,value分隔符. 3.key 不能换行,value

JavaSe:Cookie 管理的API介绍

CookieManager 在使用HttpURLConnection中,并没有关于Cookie的管理.如果使用Java程序时,怎么管理cookie呢? Cookie案例 1. User Agent -> Server POST /acme/login HTTP/1.1 [form data] 2. Server -> User Agent HTTP/1.1 200 OK Set-Cookie2: Customer="WILE_E_COYOTE"; Version="

JavaSE:命名规则、进制转换、原码补码反码、数据类型以及转换

1:关键字(掌握) (1)被Java语言赋予特定含义的单词 (2)特点: 全部小写. (3)注意事项: A:goto和const作为保留字存在. B:类似于Notepad++这样的高级记事本会对关键字有特殊颜色标记 2:标识符(掌握) (1)就是给类,接口,方法,变量等起名字的字符序列 (2)组成规则: A:英文大小写字母 B:数字 C:$和_ (3)注意事项: A:不能以数字开头 B:不能是java中的关键字 C:区分大小写 (4)常见的命名规则(见名知意) A:包 全部小写 单级包:小写 举

Java 系列文章

[Java 继承.多态] JavaSE:你真的了解继承.重写.可见性吗? [Java 集合] Java Se :线性表 Java Se :Map 系列 JavaSe:Properties文件格式 [Java Annotation] Java Annotation 学习 [Java JDBC] JDBC Driver Types JDBC API Description [Java agent,debug,manifest] Java:Remote Debug JavaSe:-javaagent,

配置文件:mainfest.xml

AndroidManifest.xml 是每个android程序中必须的文件. 它位于整个项目的根目录,描述了package中暴露的组件(activities,services, 等等),他们各自的实现类,各种能被处理的数据和启动位置. 除了能声明程序中的Activities, ContentProviders, Services,和Intent Receivers,还能指permissions和instrumentation(安全控制和测试)定定permissions和instrumentat