本文衔接上文,讲解 Byteman 注入点的功能,也就是前文提到的条件。

AT LINE

AT LINE 说明符将触发点定位在触发方法中第一个可执行字节码指令之前,其源代码行号大于或等于说明符参数中提供的行号。如果没有在(或之后)指定的行号处的可执行代码,代理将不会插入触发点(注意,在这种情况下它不会打印错误,因为这可能只是表明规则不适用于这个特定类或方法)。

AT READ

AT READ 说明符后面跟着字段名称,定位触发点在第一个出现的对象字段之前,即它对应于字节码中的第一个 getField 指令。如果指定了类型,则 getField 指令将仅在命名字段由名称与提供的类型匹配的类声明时才匹配。如果提供了计数 N,则第 N 个匹配的 getField 将被用作触发点。注意,计数标识字段访问的第 N 次文本出现,而不是在特定执行路径中的第 N 次字段访问。如果关键字 ALL 被指定代替计数,则规则将在所有匹配的 getField 调用中触发。

AT READ 说明符后面跟着一个以 $ 为前缀的局部变量名称、方法参数名称或方法参数索引,定位触发点在读取相应的局部或方法参数变量之前,即它对应于字节码中的 iloaddloadaload 等指令。如果提供了计数 N,则第 N 个匹配的读取将被用作触发点。注意,计数标识变量读取的第 N 次文本出现,而不是在特定执行路径中的第 N 次访问。如果关键字 ALL 被指定代替计数,则规则将在每次读取变量之前触发。

注意,只有在触发方法的字节码中包含了局部变量表(例如,如果它已经用 -g 标志编译),才可以使用局部或参数变量名称,如 $i, $this$arg1。相比之下,总是可以使用索引符号 $0, $1 等来引用参数变量读取操作(然而,请注意,位置 AT READ $0 将仅匹配实例方法)。

AFTER READ

AFTER READ 规范与 AT READ 规范相同,只是它将触发点定位在 getField 或变量读取操作之后。

AT WRITE, AFTER WRITE

AT WRITEAFTER WRITE 说明符与相应的 READ 说明符相同,只是它们对应于源代码中对命名字段或命名变量的赋值,即它们识别 putFieldistore, dstore, 等指令。

注意,位置 AT WRITE $0 或等效位置 AT WRITE $this 永远不会匹配任何候选触发方法,因为实例方法调用的目标对象永远不会被分配。

同样,对于给定的局部变量,位置 AT WRITE $localvar 或等效位置 AT WRITE $localvar 1 识别局部变量初始化后立即的位置,即它被视为指定为 AFTER WRITE $localvar 。这是必要的,因为变量在初始化后才有范围。这也确保了在规则正文中可以安全地访问已写入的局部变量。注意,当触发代码使用相关的调试选项编译时,代理能够将触发点范围内的局部变量作为参数传递给触发调用,使它们作为默认绑定可用。规则可以引用范围内的变量(包括方法接收者和参数),通过在它们的象征名称前加上 $ 字符,例如 $this, $arg1, $i 等。

代理还编译了围绕触发调用的异常处理程序代码,以处理规则处理过程中可能发生的异常。这不是为了处理规则执行引擎检测到的错误(它们应该全部被内部捕获和处理)。异常是从执行引擎抛出的,以改变触发方法的控制流。通常,在从触发调用返回后,触发线程继续执行原始方法代码。然而,规则可以使用返回和抛出内置动作来指定从触发方法执行早期返回或异常抛出。规则语言实现通过在触发调用下方抛出其自己的私有内部异常来实现这一点。编译到触发方法中的处理程序代码会捕获这些内部异常,然后返回给调用者或递归抛出运行时或应用程序特定的异常。这避免了触发方法主体中剩余代码的正常执行。如果触发点还有其它触发调用待处理,则这些也会被绕过。

AT INVOKE, AFTER INVOKE 调用时、调用后

AT INVOKEAFTER INVOKE 说明符类似于 READWRITE 说明符,只不过它们将触发方法内的方法或构造函数的调用标识为触发点。该方法可以使用裸方法名称来标识,或者该名称可以由可能是包限定的类型或描述符限定。描述符由括号内以逗号分隔的类型名称列表组成。类型名称标识方法参数的类型,并且可以使用包限定符作为前缀,并使用数组括号对作为后缀。

AT NEW, AFTER NEW 创建前后

AT NEWAFTER NEW 说明符标识目标方法中 new 操作创建 Java 对象类或数组类的位置。在分配对象或数组之前会触发 AT NEW 规则。创建并初始化对象或数组后会触发 AFTER NEW 规则。

NEW 触发器位置的选择可以通过提供各种可选参数、类型名称、一对或多对方括号以及整数计数或关键字 ALL 来限制。这些参数都可以独立指定,并且它们各自用于为可以考虑将规则注入到目标方法中的点选择一组或多或少精确的匹配。

如果提供了类型名称,则注入仅限于创建命名类型的实例(或数组)的点。可以在不提供包限定符的情况下提供类型名称,在这种情况下,具有共享相同非包限定名称的类型的任何新操作都将匹配。

如果省略类型名称,则注入可以在创建实例(或数组)的任何点发生。

请注意,匹配时会忽略 extendsimplements 关系。例如,如果规则指定 AT NEW Foo 则即使 FooBar extends Foo 该位置也不会与操作 new Foobar 匹配。类似地,当 Foo implements IFoo 时,指定位置 AT NEW IFoo 将不会匹配。事实上,指定任何接口都是一个错误。新操作总是实例化特定的类,而不是接口。因此,指定接口名称的位置永远不会匹配。

如果包含一对或多对大括号,则注入仅限于方法中创建具有相同维数的数组的点。因此,例如指定 AT NEW [][] 将匹配创建 2d 数组的任何新操作,无论数组基本类型是什么,相比之下,指定 AT NEW int[] 将仅匹配创建 1d 数组的新操作 int 数组已创建。如果没有提供大括号,则匹配将仅限于实例化 Java 对象类(即非数组类)的新操作。

当方法中有多个候选注入点时,可以提供整数计数来选择特定的注入点(如果未指定,则计数默认为 1)。可以提供关键字 ALL 以请求所有匹配注入点的注入。

AT SYNCHRONIZE, AFTER SYNCHRONIZE 同步时、同步后

AT SYNCHRONIZEAFTER SYNCHRONIZE 说明符标识目标方法中的同步块,即它们对应于字节码中的 MONITORENTER 指令。请注意,AFTER SYNCHRONIZE 标识紧接着进入同步块之后的点,而不是紧接着退出同步块之后的点。

AT THROW 抛出

AT THROW 说明符将触发方法内的抛出操作标识为触发点。抛出操作可以由标识所抛出异常的词法类型的类型名(可能是包限定的)来限定。如果提供了计数 N,则该位置指定抛出的第 N 个文本出现。如果指定关键字 ALL 来代替计数,则将在所有匹配的抛出事件发生时触发该规则。

AT EXCEPTION EXIT 异常退出

AT EXCEPTION EXIT 说明符标识方法通过未处理的异常控制流将控制返回给其调用者的点。发生这种情况的原因可能是该方法本身引发了异常,也可能是因为它调用了引发异常的其他方法。当方法在 Java 语言中执行某些操作时也可能发生这种情况,例如取消引用空对象值或索引超出数组末尾。

在此位置注入的规则将在异常通常传播回调用者的点触发。一旦规则执行完成,异常流程通常会恢复。然而,该规则可能会通过执行 RETURN 来破坏此恢复的流程。它还可以显式地重新抛出原始异常或通过执行 THROW 抛出一些新创建的异常(注意,如果后者是受检查异常,则必须通过触发方法将其声明为可能的异常)。

注:当多个规则指定相同位置时,触发器调用的注入顺序通常遵循各自脚本中规则的顺序。例外情况是 AFTER 位置,其中注入顺序与发生顺序相反。

注:当位置说明符(ENTRYEXIT 除外)与重写规则一起使用时,如果位置与相关方法匹配,则规则代码仅会注入到原始方法或重写方法中。因此,例如,如果采用位置 AT READ myField 2,则该规则将仅被注入到包含两次字段 myField 加载的方法的实现中。与位置不匹配的方法将被忽略。

由于历史原因,CALL 可以用作 INVOKE 的同义词,RETURN 可以用作 EXIT 的同义词,并且 AT LINE 说明符中的 AT 是可选的。

FunTester 原创精华

【连载】从 Java 开始性能测试


↙↙↙阅读原文可查看相关链接,并与作者交流