Instrumentation Features (javaagent)

The use of  Java  code that is, do java.lang.instrument Instrumentation is dynamic  Java SE  new 5 features, it is the function of Java's instrument of liberation from native code in it, so that it can solve the problem by way of Java code. Using the Instrumentation, a developer can build an application-independent Agent (- Agent), for monitoring and assisting programs running on the JVM, alternatives and modifications can even define certain class. With this feature, developers can implement class Java virtual machine monitor and operate a more flexible operation, this feature actually provides a virtual machine level support of AOP implementation, so that developers do not need to JDK do any upgrades and changes, we can achieve some of the functions of the AOP.

In  the Java  SE. 6 which, Instrumentation packet is given a more powerful: instrument after startup, native code (native code) instrument, and the like, and dynamically change the classpath. These changes mean that Java has a more dynamic control, explanatory power, it makes the Java language has become more flexible.

In Java SE6 inside, the biggest change Instrumentation runtime possible. In the Java SE 5, Instrument required before operating with the command line parameters or system parameters to set the proxy class, the actual running in the virtual machine at the time of initialization (before being loaded in most Java class libraries) , instrumentation setup has been launched, in the virtual machine and set up a callback function to detect a specific type of loading conditions, and do the actual work. However, in many practical situations, we have no way to set their proxy at the start of the virtual machine, so in fact limit the application of the instrument. The new features of Java SE 6 has changed this situation, the Java Tool API is attach way, we can easily set up a proxy class is loaded dynamically at runtime, to achieve the purpose of instrumentation.

In addition, Instrumentation for native is also a new Java SE 6 features, which can not be completed before the function - instrumentation for native interface can be in Java SE 6, or by a series of add prefix has been accomplished.

Finally, Java SE 6 in the Instrumentation also increased dynamically add class path functions. All these new features that have made functional instrument pack more abundant, so that the Java language itself more powerful.

Back to top

Instrumentation basic functions and usage

"Java.lang.instrument" package embodied, depending on JVMTI. JVMTI (Java Virtual Machine Tool Interface) is a collection of local programming interface provided by a Java Virtual Machine, JVM-related tools provided. JVMTI is introduced from the beginning of Java SE 5, integration and replaces the previous use of the Java Virtual Machine Profiler Interface (JVMPI) and the Java Virtual Machine Debug Interface (JVMDI ), whereas in Java SE 6, JVMPI and JVMDI has disappeared. JVMTI provides a "proxy" procedural mechanism to support third-party utilities to connect and access proxy JVM, and take advantage of the rich programming interface JVMTI provides complete many functions associated with the JVM. In fact, to achieve java.lang.instrument package, which is based on this mechanism: the realization among Instrumentation, there is a JVMTI agent by calling JVMTI Java classes related functions to complete the dynamic operation among Java classes. Aside from Instrumentation functions, JVMTI is still virtual machine memory management, thread control, operational methods and variables, etc., provides a wealth of valuable functions. For more information about JVMTI, see Java SE 6 documentation (see  Resources ) introduced among.

Instrumentation of the maximum effect is to change the dynamic class definitions and operations. In Java SE 5 and later among the developers in an ordinary Java program (Java class with a main function) is running, by  – javaagentspecifying a specific parameter jar file (containing Instrumentation agent) to initiate Instrumentation Agent.

In Java SE 5, so that developers can make Instrumentation proxy executed before the main function is run. Briefly to say is the following steps:

  1. Write function premain

    Write a Java class, comprising the following two methods among any

    public static void premain(String agentArgs, Instrumentation inst); [1] public static void premain(String agentArgs); [2]
      

    Wherein, [1] is higher than the priority [2], the priority will be executed ([1] and [2] exist, [2] is ignored).

    In this premain function, developers can perform various operations of the class.

    agentArgs program parameters premain function is obtained, along with " – javaagent" Incoming together. The difference is that the main function, this parameter is a string and not a string array, if there are a plurality of program parameters, the program parses the string itself.

    Inst instance java.lang.instrument.Instrumentation is automatically passed by the JVM. java.lang.instrument.Instrumentation instrument is an interface defined in the package, but also the core portion of the packet, almost all of which focus on the features methods, such as class definition and conversion operations and the like.

  2. jar file packaging

    The Java class file packaged into a jar, and wherein the manifest attribute among the added "Premain-Class" to specify that the Java class procedure 1 with premain prepared. (You may also need to specify additional attributes to turn on more features)

  3. run

    With running Java programs with the following way of Instrumentation:

    java -javaagent:jar 文件的位置 [= 传入 premain 的参数 ]

The operation of Java class files, can be understood as the operation of a byte array (binary class file byte stream into a byte array). Developers can "ClassFileTransformer" among the obtained transform method, and the operation returns ultimately define a class (a byte array). In this regard, Apache's BCEL open source project provides a strong support, the reader can refer to the article " the Java SE 5 properties Instrumentation practice see an example of a combination of BCEL and Instrumentation" in the. Specific focus bytecode manipulation herein is not, therefore, in the examples cited herein, except that a simple way to demonstrate alternative class file using the Instrumentation.

Here, we have a simple way of example, to illustrate the basic use of Instrumentation.

First of all, we have a simple class, TransClass, you can return an integer 1 through a static method.

public class TransClass { 
	 public int getNumber() { return 1; } }

 

We run the following class, you can get the output "1."

public class TestMainInJar { 
    public static void main(String[] args) { System.out.println(new TransClass().getNumber()); } }

 

Then, we will TransClass method of getNumber read as follows:

public int getNumber() { return 2; }

 

And then return to this document Java 2 compiled into class files, in order to distinguish the original return class 1, we will return to this class 2 file named TransClass2.class.2.

Next, we create a Transformer class:

import java.io.File; 
 import java.io.FileInputStream; 
 import java.io.IOException; 
 import java.io.InputStream; 
 import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; class Transformer implements ClassFileTransformer { public static final String classNumberReturns2 = "TransClass.class.2"; public static byte[] getBytesFromFile(String fileName) { try { // precondition File file = new File(fileName); InputStream is = new FileInputStream(file); long length = file.length(); byte[] bytes = new byte[(int) length]; // Read in the bytes int offset = 0; int numRead = 0; while (offset <bytes.length && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) { offset += numRead; } if (offset < bytes.length) { throw new IOException("Could not completely read file " + file.getName()); } is.close(); return bytes; } catch (Exception e) { System.out.println("error occurs in _ClassTransformer!" + e.getClass().getName()); return null; } } public byte[] transform(ClassLoader l, String className, Class<?> c, ProtectionDomain pd, byte[] b) throws IllegalClassFormatException { if (!className.equals("TransClass")) { return null; } return getBytesFromFile(classNumberReturns2); } }

 

This class implements ClassFileTransformer interface. Wherein, getBytesFromFile transform method Method file name reads binary character stream, but which ClassFileTransformer predetermined conversion is complete replacement class definition.

Finally, we build a Premain class, write the proxy method premain Instrumentation:

public class Premain { 
    public static void premain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException { inst.addTransformer(new Transformer()); } }

 

As can be seen, addTransformer does not specify the method to which class switching. Conversion occurs after premain function executes, before the main function is executed, each time a class loading, Transform method will be executed once, to see whether the conversion, therefore, the transform (Transformer class) method, the program using className.equals ( "TransClass") to determine whether to convert the current class.

After the code is complete, we will package them as TestInstrument1.jar. TransClass 1 returns the class files remain in the jar package, and return the TransClass.class.2 2 is placed outside the jar. Add the following property class manifest to specify which premain located:

Manifest-Version: 1.0 
 Premain-Class: Premain

 

When you run the program, if we run the jar in the main function with the normal way, you can get the output "1." If you run the following ways:

java – javaagent:TestInstrument1.jar – cp TestInstrument1.jar TestMainInJar

 

Output will get "2."

Of course, main function of the program is running does not have to be placed premain located inside the jar file, here is an example for the convenience of packaged and put together.

Open with addTransformer manner, Instrumentation which there is another method of "redefineClasses" premain achieved among other specified conversion. Usage is similar to the following:

public class Premain { 
    public static void premain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException { ClassDefinition def = new ClassDefinition(TransClass.class, Transformer .getBytesFromFile(Transformer.classNumberReturns2)); inst.redefineClasses(new ClassDefinition[] { def }); System.out.println("success"); } }

 

redefineClasses more powerful features, it can batch convert a lot of class.

Back to top

Java SE 6 new features: Dynamic instrument after the virtual machine starts

In Java SE 5, so that developers can only display their imagination in premain them, Instrumentation also made only with the former main function is executed, there are some limitations in this way.

On the basis of the Java SE 5, Java SE 6 for this situation made improvements, the developer can begin after the main function, and then start your own Instrumentation program.

In Instrumentation Java SE 6, among them, with a premain "parallel" "agentmain" method, it can run again after the main function starts running.

Premain with the same function, developers can write a contain "agentmain" function Java class:

public static void agentmain (String agentArgs, Instrumentation inst); [1] public static void agentmain (String agentArgs); [2]

 

Likewise, [1] is higher than the priority [2], the priority will be executed.

Premain with the same function, developers can perform various operations of the class in agentmain in. Which agentArgs and Inst same usage with premain.

And "Premain-Class" Similarly, the developer must set the "Agent-Class" specified in the manifest file which contains class agentmain function.

However, with premain difference it is, agentmain need to start at the main function begins to run only after, how it should determine such a time, such a function and how to achieve it?

In Java SE 6 documentation among developers may not be able to see clearly described in the documentation section java.lang.instrument package related, more specific application can not see agnetmain example. However, the new features in Java SE 6's inside, not from a place of eyes, reveals agentmain usage. This is among the Attach API Java SE 6 provides.

Attach API is not a Java standard API, but a set of extensions provided by Sun API, used to target JVM "attachment" (Attach) proxy tool program. With it, developers can easily monitor a JVM, plus run a proxy program.

Attach API is very simple, only two main classes, which are com.sun.tools.attach package: VirtualMachine on behalf of a Java virtual machine, which is the need to monitor the program's target virtual machine, JVM provides enumeration, Attach and action Detach operation (the opposite behavior Attach operation, agent is released from a top JVM) and the like; VirtualMachineDescriptor is described in a virtual machine container class, with the class VirtualMachine perform various functions.

For simplicity, we simplify the following example: class file with still an alternative embodiment, a function will return a return replace function 2, Attach API written in a thread inside, a sleep mode waiting for every half a second inspection once all of the Java virtual machine, when there is time to find a new virtual machine appears, call attach function, then reloading Jar file in accordance with the attach API documentation inside the said manner. Wait for 5 seconds when, attach automatically end. In which the main function, the program returns once every half a second output value (indicating a return value from a 1 to 2).

Transformer TransClass classes and class code unchanged, with reference to the previous section. TestMainInJar containing the code for the main function of:

public class TestMainInJar { 
    public static void main(String[] args) throws InterruptedException { System.out.println(new TransClass().getNumber()); int count = 0; while (true) { Thread.sleep(500); count++; int number = new TransClass().getNumber(); System.out.println(number); if (3 == number || count >= 10) { break; } } } }

 

AgentMain class containing the code is agentmain:

import java.lang.instrument.ClassDefinition; 
 import java.lang.instrument.Instrumentation; 
 import java.lang.instrument.UnmodifiableClassException; 

 public class AgentMain { public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException, InterruptedException { inst.addTransformer(new Transformer (), true); inst.retransformClasses(TransClass.class); System.out.println("Agent Main Done"); } }

 

Which, retransformClasses is a new method inside Java SE 6, which like redefineClasses, you can batch convert class definitions, and more for agentmain occasions.

Jar file with the example Premain inside the Jar file almost as well as the main and agentmain classes, TransClass, Transformer and other categories put together, packaged as "TestInstrument1.jar", and the Manifest file Jar file which is:

Manifest-Version: 1.0 
 Agent-Class: AgentMain

 

Further, in order to run Attach API, we can write a control program to simulate the monitoring process :( snippet)

import com.sun.tools.attach.VirtualMachine; 
 import com.sun.tools.attach.VirtualMachineDescriptor; 
……
 // 一个运行 Attach API 的线程子类
 static class AttachThread extends Thread { private final List<VirtualMachineDescriptor> listBefore; private final String jar; AttachThread(String attachJar, List<VirtualMachineDescriptor> vms) { listBefore = vms; // 记录程序启动时的 VM 集合 jar = attachJar; } public void run() { VirtualMachine vm = null; List<VirtualMachineDescriptor> listAfter = null; try { int count = 0; while (true) { listAfter = VirtualMachine.list(); for (VirtualMachineDescriptor vmd : listAfter) { if (!listBefore.contains(vmd)) { // 如果 VM 有增加,我们就认为是被监控的 VM 启动了 // 这时,我们开始监控这个 VM vm = VirtualMachine.attach(vmd); break; } } Thread.sleep(500); count++; if (null != vm || count >= 10) { break; } } vm.loadAgent(jar); vm.detach(); } catch (Exception e) { ignore } } } …… public static void main(String[] args) throws InterruptedException { new AttachThread("TestInstrument1.jar", VirtualMachine.list()).start(); }

 

Runtime, can run on top of the first to start a new thread main function, and then, within 5 seconds (just a simple simulation of the monitoring process JVM) run the following command to start the test Jar file:

java – javaagent:TestInstrument2.jar – cp TestInstrument2.jar TestMainInJar

 

If the time to master Debu too bad, then, the program first played on the screen 1, which is the output of the class before the changes, and then will play some 2, this represents agentmain Attach API has been successfully attached to the JVM, the agent in force of course, you can also see the output "Agent Main Done" word.

The above example is just a simple example, a brief description of this feature only. Real-life examples tend to be more complex and may run in a distributed among multiple JVM environment.

Back to top

Java SE 6 new features: Instrumentation native method

In version 1.5 of instumentation years, and no handling of Java native method (Native Method), and under the Java standard JVMTI, and there is no way to change the method signature, which makes very difficult to replace the local method. A more direct and simple idea is to replace the dynamic link library native code is located at the start - but this is essentially a static replacement, rather than dynamically Instrumentation. Moreover, this may need to compile a large number of dynamic link libraries - for example, we have three local function, assuming that each one needs a replacement, but under different applications may require different combinations, so if we put three functions are compiled in the same dynamic link library, we need up to eight different dynamic link library to meet the needs. Of course, we can also be compiled independently of, those require six dynamic link libraries - in any case, such a cumbersome way is unacceptable.

In Java SE 6, the new Native Instrumentation presents a new analytical methods native code as a way to supplement resolve the original native method, and to a good solution to some problems. This is the new version of java.lang.instrument bag, we have the instrument way of native code - set prefix.

Suppose we have a native function, called nativeMethod, in the course of operation, we need to point it to another function (It should be noted that, under the current standard of JVMTI, in addition to native function name, the other signature needs to be consistent ). For example, our Java code is:

package nativeTester; 
 class nativePrefixTester{ 
	…
	 native int nativeMethod(int input); … }

 

Then the local code that we have achieved are:

jint Java_nativeTester_nativeMethod(jclass thiz, jobject thisObj, jint input);

 

Now we need when calling this function, to point to another function. Well, according to J2SE practice, we can according to his naming, plus a new function name as a prefix. For example, we "another_" as a prefix, then our new functions are:

jint Java_nativeTester_another_nativePrefixTester(jclass thiz, jobject thisObj, 
 jint input);

 

Then it incorporated into the dynamic link library.

Now that we have a new local function, the next step is to make instrument settings. As mentioned above, we can use premain way, when the virtual machine is powered on the instrument complete load premain proxy settings. You can also use agentmain way to attach virtual machine to start the agent. The native function set is quite simple:

the premain () {   // or may be in the agentmain
 (! isNativeMethodPrefixSupported ()) IF { 
		  return; // If you can not set, then returns
 }  setNativeMethodPrefix (the Transformer, "another_"); // set the native function of the prefix, note that this provision must be underlined by the users themselves ...}

 

Here we pay attention to two issues. One is not, in any case all be set prefix native function of. First of all, we have to note that among the characteristics of Manifest agent package set:

Can-Set-Native-Method-Prefix

 

Note that this parameter can affect whether you can set up native prefix, and, in the default settings, this parameter is false, we need to set it to true (by the way, among the attributes of the Manifest for are case-insensitive, of course, if not to a value of "true" would be considered a false value processing).

Of course, we also need to confirm whether the virtual machine itself supports setNativePrefix. In the Java API years, Instrumentation class provides a function isNativePrefix, through this function we can know whether the feature can be implemented.

二是我们可以为每一个 ClassTransformer 加上它自己的 nativeprefix;同时,每一个 ClassTransformer 都可以为同一个 class 做 transform,因此对于一个 Class 来说,一个 native 函数可能有不同的 prefix,因此对这个函数来说,它可能也有好几种解析方式。

在 Java SE 6 当中,Native prefix 的解释方式如下:对于某一个 package 内的一个 class 当中的一个 native method 来说,首先,假设我们对这个函数的 transformer 设置了 native 的 prefix“another”,它将这个函数接口解释成 :

由 Java 的函数接口

native void method()

 

和上述 prefix"another",去寻找本地代码中的函数

void Java_package_class_another_method(jclass theClass, jobject thiz);  
 // 请注意 prefix 在函数名中出现的位置!

 

一旦可以找到,那么调用这个函数,整个解析过程就结束了;如果没有找到,那么虚拟机将会做进一步的解析工作。我们将利用 Java native 接口最基本的解析方式 , 去找本地代码中的函数 :

void Java_package_class_method(jclass theClass, jobject thiz);

 

如果找到,则执行之。否则,因为没有任何一个合适的解析方式,于是宣告这个过程失败。

那么如果有多个 transformer,同时每一个都有自己的 prefix,又该如何解析呢?事实上,虚拟机是按 transformer 被加入到的 Instrumentation 之中的次序去解析的(还记得我们最基本的 addTransformer 方法吗?)。

假设我们有三个 transformer 要被加入进来,他们的次序和相对应的 prefix 分别为:transformer1 和“prefix1_”,transformer2 和 “prefix2_”,transformer3 和 “prefix3_”。那么,虚拟机会首先做的就是将接口解析为 :

native void prefix1_prefix2_prefix3_native_method()

 

然后去找它相对应的 native 代码。

但是如果第二个 transformer(transformer2)没有设定 prefix,那么很简单,我们得到的解析是:

native void prefix1_prefix3_native_method()

 

这个方式简单而自然。

当然,对于多个 prefix 的情况,我们还要注意一些复杂的情况。比如,假设我们有一个 native 函数接口是:

native void native_method()

 

然后我们为它设置了两个 prefix,比如 "wrapped_" 和 "wrapped2_",那么,我们得到的是什么呢?是

void Java_package_class_wrapped_wrapped2_method(jclass theClass, jobject thiz); 
 // 这个函数名正确吗?

 

吗?答案是否定的,因为事实上,对 Java 中 native 函数的接口到 native 中的映射,有一系列的规定,因此可能有一些特殊的字符要被代入。而实际中,这个函数的正确的函数名是:

void Java_package_class_wrapped_1wrapped2_1method(jclass theClass, jobject thiz); 
 // 只有这个函数名会被找到

 

很有趣不是吗?因此如果我们要做类似的工作,一个很好的建议是首先在 Java 中写一个带 prefix 的 native 接口,用 javah 工具生成一个 c 的 header-file,看看它实际解析得到的函数名是什么,这样我们就可以避免一些不必要的麻烦。

另外一个事实是,与我们的想像不同,对于两个或者两个以上的 prefix,虚拟机并不做更多的解析;它不会试图去掉某一个 prefix,再来组装函数接口。它做且仅作两次解析。

总之,新的 native 的 prefix-instrumentation 的方式,改变了以前 Java 中 native 代码无法动态改变的缺点。在当前,利用 JNI 来写 native 代码也是 Java 应用中非常重要的一个环节,因此它的动态化意味着整个 Java 都可以动态改变了 —— 现在我们的代码可以利用加上 prefix 来动态改变 native 函数的指向,正如上面所说的,如果找不到,虚拟机还会去尝试做标准的解析,这让我们拥有了动态地替换 native 代码的方式,我们可以将许多带不同 prefix 的函数编译在一个动态链接库之中,而通过 instrument 包的功能,让 native 函数和 Java 函数一样动态改变、动态替换。

当然,现在的 native 的 instrumentation 还有一些限制条件,比如,不同的 transformer 会有自己的 native prefix,就是说,每一个 transformer 会负责他所替换的所有类而不是特定类的 prefix —— 因此这个粒度可能不够精确。

回页首

Java SE 6 新特性:BootClassPath / SystemClassPath 的动态增补

我们知道,通过设置系统参数或者通过虚拟机启动参数,我们可以设置一个虚拟机运行时的 boot class 加载路径(-Xbootclasspath)和 system class(-cp)加载路径。当然,我们在运行之后无法替换它。然而,我们也许有时候要需要把某些 jar 加载到 bootclasspath 之中,而我们无法应用上述两个方法;或者我们需要在虚拟机启动之后来加载某些 jar 进入 bootclasspath。在 Java SE 6 之中,我们可以做到这一点了。

实现这几点很简单,首先,我们依然需要确认虚拟机已经支持这个功能,然后在 premain/agantmain 之中加上需要的 classpath。我们可以在我们的 Transformer 里使用 appendToBootstrapClassLoaderSearch/appendToSystemClassLoaderSearch 来完成这个任务。

同时我们可以注意到,在 agent 的 manifest 里加入 Boot-Class-Path 其实一样可以在动态地载入 agent 的同时加入自己的 boot class 路径,当然,在 Java code 中它可以更加动态方便和智能地完成 —— 我们可以很方便地加入判断和选择成分。

在这里我们也需要注意几点。首先,我们加入到 classpath 的 jar 文件中不应当带有任何和系统的 instrumentation 有关的系统同名类,不然,一切都陷入不可预料之中 —— 这不是一个工程师想要得到的结果,不是吗?

其次,我们要注意到虚拟机的 ClassLoader 的工作方式,它会记载解析结果。比如,我们曾经要求读入某个类 someclass,但是失败了,ClassLoader 会记得这一点。即使我们在后面动态地加入了某一个 jar,含有这个类,ClassLoader 依然会认为我们无法解析这个类,与上次出错的相同的错误会被报告。

再次我们知道在 Java 语言中有一个系统参数“java.class.path”,这个 property 里面记录了我们当前的 classpath,但是,我们使用这两个函数,虽然真正地改变了实际的 classpath,却不会对这个 property 本身产生任何影响。

在公开的 JavaDoc 中我们可以发现一个很有意思的事情,Sun 的设计师们告诉我们,这个功能事实上依赖于 ClassLoader 的 appendtoClassPathForInstrumentation 方法 —— 这是一个非公开的函数,因此我们不建议直接(使用反射等方式)使用它,事实上,instrument 包里的这两个函数已经可以很好的解决我们的问题了。

回页首

结语

从以上的介绍我们可以得出结论,在 Java SE 6 里面,instrumentation 包新增的功能 —— 虚拟机启动后的动态 instrument、本地代码(native code)instrumentation,以及动态添加 classpath 等等,使得 Java 具有了更强的动态控制、解释能力,从而让 Java 语言变得更加灵活多变。

这些能力,从某种意义上开始改变 Java 语言本身。在过去很长的一段时间内,动态 脚本语言的大量涌现和快速发展,对整个软件业和网络业提高生产率起到了非常重要的作用。在这种背景之下,Java 也正在慢慢地作出改变。而 Instrument 的新功能和 Script 平台(本系列的后面一篇中将介绍到这一点)的出现,则大大强化了语言的动态化和与动态语言融合,它是 Java 的发展的值得考量的新趋势。

转载于:https://www.cnblogs.com/licheng/p/6576644.html

Guess you like

Origin blog.csdn.net/weixin_33831196/article/details/92629989