Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to understand Instrumentation in Java SE 6

2025-01-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article introduces you how to understand Instrumentation in Java SE 6, the content is very detailed, interested friends can refer to, hope to be helpful to you.

With Instrumentation, developers can build an application-independent agent (Agent) to monitor and assist programs running on JVM, and even replace and modify the definitions of certain classes. With such features, developers can achieve more flexible runtime virtual machine monitoring and Java class operations, this feature actually provides a virtual machine-level support for AOP implementation, so that developers do not need to make any upgrades and changes to JDK, can achieve some AOP functions.

In Java SE 6, the instrumentation package is endowed with more powerful functions: instrument after startup, native code (native code) instrument, and dynamically changing classpath, and so on. These changes mean that Java has stronger dynamic control and interpretation ability, and it makes the Java language more flexible.

In Java SE6, the new changes make runtime Instrumentation possible. In Java SE 5, Instrument requires the use of command line parameters or system parameters to set the proxy class before running. In the actual operation, when the virtual machine is initialized (before most of the Java class libraries are loaded), the setting of instrumentation has been started, and the callback function is set in the virtual machine to detect the loading of specific classes and complete the actual work. But in many practical cases, there is no way to set a proxy for the virtual machine when it is started, which actually limits the application of instrument. The new features of Java SE 6 change this situation. Through the attach mode in Java Tool API, we can easily set the loading proxy class dynamically during the running process to achieve the purpose of instrumentation.

In addition, Instrumentation for native is also a new feature of Java SE 6, which allows instrumentation to the native interface to be done in Java SE 6 through one or a series of prefix additions that were previously impossible.

* Instrumentation in Java SE 6 also adds the ability to dynamically add class path. All these new features make the functionality of the instrument package richer, thus making the Java language itself more powerful.

Basic functions and usage of Instrumentation

The specific implementation of the "java.lang.instrument" package depends on JVMTI. JVMTI (Java Virtual Machine Tool Interface) is a set of native programming interfaces provided by the Java virtual machine for JVM-related tools. JVMTI was introduced in Java SE 5, integrating and replacing the previously used Java Virtual Machine Profiler Interface (JVMPI) and the Java Virtual Machine Debug Interface (JVMDI), while in Java SE 6, JVMPI and JVMDI have disappeared. JVMTI provides a set of "agent" program mechanism, which can support third-party tool programs to connect and access JVM in a proxy way, and make use of the rich programming interfaces provided by JVMTI to complete many functions related to JVM. In fact, the implementation of java.lang.instrument package is based on this mechanism: in the implementation of Instrumentation, there is a JVMTI agent program, which completes the dynamic operation of Java class by calling the functions related to Java class in JVMTI. In addition to the Instrumentation function, JVMTI also provides a large number of valuable functions in virtual machine memory management, thread control, methods and variable manipulation, and so on.

The role of Instrumentation is to dynamically change and manipulate class definitions. In Java SE 5 and subsequent versions, developers can start the Instrumentation agent by specifying a specific jar file (containing the Instrumentation agent) through the-javaagent parameter when a normal Java program (Java class with main function) is running.

In Java SE 5, developers can have the Instrumentation agent execute before the main function runs. In a nutshell, the steps are as follows:

Write premain function

Write a Java class that contains either of the following two methods

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

Public static void premain (String agentArgs); [2]

Among them, [1] has a higher priority than [2] and will be given priority (when [1] and [2] exist at the same time, [2] is ignored).

In this premain function, the developer can perform various operations on the class.

AgentArgs is a program parameter obtained by the premain function, passed along with "- javaagent". Unlike the main function, this parameter is a string rather than an array of strings, and if there are multiple arguments to the program, the program parses the string itself.

Inst is an instance of java.lang.instrument.Instrumentation that is automatically passed in by JVM. Java.lang.instrument.Instrumentation is an interface defined in the instrument package and the core of the package, centralizing almost all of its functional methods, such as the transformation and manipulation of class definitions, and so on.

Jar file packaging

Package the Java class into a jar file and add "Premain-Class" to the manifest property to specify the Java class with premain written in step 1. (you may also need to specify additional properties to turn on more functionality)

Running

Run the Java program with Instrumentation as follows:

Location of java-javaagent:jar file [= parameters passed in premain]

The operation on the Java class file can be understood as the operation on a byte array (reading the binary byte stream of the class file into a byte array). Developers can get, manipulate, and eventually return a class definition (a byte array) in the transform method of "ClassFileTransformer". In this regard, Apache's BCEL open source project provides strong support, and readers can see an example of the combination of BCEL and Instrumentation in the reference article "Java SE 5 feature Instrumentation practice." Specific bytecode manipulation is not the focus of this article, so the examples given in this article only use a simple replacement of class files to demonstrate the use of Instrumentation.

Below, we use a simple example to illustrate the basic use of Instrumentation.

First, we have a simple class, TransClass, that returns an integer of 1 through a static method.

Public class TransClass {

Public int getNumber () {

Return 1

}

}

We run the following class to get the output "1".

Public class TestMainInJar {

Public static void main (String [] args) {

System.out.println (new TransClass () .getNumber ())

}

}

Then, we change the getNumber method of TransClass to the following:

Public int getNumber () {

Return 2

}

Then compile the Java file that returns 2 into a class file. In order to distinguish the original class that returns 1, we name the class file that returns 2 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.Pro??? What? TectionDomain

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= 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);  } } 这个类实现了 ClassFileTransformer 接口。其中,getBytesFromFile 方法根据文件名读入二进制字符流,而 ClassFileTransformer 当中规定的 transform 方法则完成了类定义的替换转换。 ***,我们建立一个 Premain 类,写入 Instrumentation 的代理方法 premain: public class Premain {  public static void premain(String agentArgs, Instrumentation inst)  throws ClassNotFoundException, UnmodifiableClassException { inst.addTransformer(new Transformer());  } } 可以看出,addTransformer 方法并没有指明要转换哪个类。转换发生在 premain 函数执行之后,main 函数执行之前,这时每装载一个类,transform 方法就会执行一次,看看是否需要转换,所以,在 transform(Transformer 类中)方法中,程序用 className.equals("TransClass") 来判断当前的类是否需要转换。 代码完成后,我们将他们打包为 TestInstrument1.jar。返回 1 的那个 TransClass 的类文件保留在 jar 包中,而返回 2 的那个 TransClass.class.2 则放到 jar 的外面。在 manifest 里面加入如下属性来指定 premain 所在的类: Manifest-Version: 1.0 Premain-Class: Premain 在运行这个程序的时候,如果我们用普通方式运行这个 jar 中的???椠?? main 函数,可以得到输出"1"。如果用下列方式运行: java -javaagent:TestInstrument1.jar -cp TestInstrument1.jar TestMainInJar 则会得到输出"2"。 当然,程序运行的 main 函数不一定要放在 premain 所在的这个 jar 文件里面,这里只是为了例子程序打包的方便而放在一起的。 除开用 addTransformer 的方式,Instrumentation 当中还有另外一个方法"redefineClasses"来实现 premain 当中指定的转换。用法类似,如下: 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 的功能比较强大,可以批量转换很多类。 Java SE 6 的新特性:虚拟机启动后的动态 instrument 在 Java SE 5 当中,开发者只能在 premain 当中施展想象力,所作的 Instrumentation 也仅限与 main 函数执行前,这样的方式存在一定的局限性。 在 Java SE 5 的基础上,Java SE 6 针对这种状况做出了改进,开发者可以在 main 函数开始执行以后,再启动自己的 Instrumentation 程序。 在 Java SE 6 的 Instrumentation 当中,有一个跟 premain"并驾齐驱"的"agentmain"方法,可以在 main 函数开始运行之后再运行。 跟 premain 函数一样, 开发者可以编写一个含有"agentmain"函数的 Java 类: public static void agentmain (String agentArgs, Instrumentation inst); [1] public static void agentmain (String agentArgs); [2] 同样,[1] 的优先级比 [2] 高,将会被优先执行。 跟 premain 函数一样,开发者可以在 agentmain 中进行对类的各种操作。其中的 agentArgs 和 Inst 的用法跟 premain 相同。 与"Premain-Class"类似,开发者必须在 manifest 文件里面设置"Agent-Class"来指定包含 agentmain 函数的类。 可是,跟 premain 不同的是,agentmain 需要在 main 函数开始运行后才启动,这样的时机应该如何确定呢,这样的功能又如何实现呢? 在 Java SE 6 文档当中,开发者也许无法在 java.lang.instrument 包相关的文档部分看到明确的介绍,更加无法看到具体的应用 agnetmain 的例子。不过,在 Java SE 6 的新特性里面,有一个不太起眼的地方,揭示了 agentmain 的用法。这就是 Java SE 6 当中提供的 Attach API。 Attach API 不是 Java 的标准 API,而是 Sun 公司提供的一套扩展 API,用来向目标 JVM "附着"(Attach)代理工具程序的。有了它,开发者可以方便的监控一个 JVM,运行一个外加的代理程序。 Attach API 很简单,只有 2 个主要的类,都在 com.sun.tools.attach 包里面: VirtualMachine 代表一个 Java 虚拟机,也就是程序需要监控的目标虚拟机,提供了 JVM 枚举,Attach 动作和 Detach 动作(Attach 动作的相反行为,从 JVM 上面解除一???椠?? 个代理)等等; VirtualMachineDescriptor 则是一个描述虚拟机的容器类,配合 VirtualMachine 类完成各种功能。 为了简单起见,我们举例简化如下:依然用类文件替换的方式,将一个返回 1 的函数替换成返回 2 的函数,Attach API 写在一个线程里面,用睡眠等待的方式,每隔半秒时间检查一次所有的 Java 虚拟机,当发现有新的虚拟机出现的时候,就调用 attach 函数,随后再按照 Attach API 文档里面所说的方式装载 Jar 文件。等到 5 秒钟的时候,attach 程序自动结束。而在 main 函数里面,程序每隔半秒钟输出一次返回值(显示出返回值从 1 变成 2)。 TransClass 类和 Transformer 类的代码不变,参看上一节介绍。 含有 main 函数的 TestMainInJar 代码为: 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

}

}

}

}

The code for the AgentMain class that contains agentmain is:

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")

}

}

Among them, retransformClasses is a new method in Java SE 6. Like redefineClasses, it can convert class definitions in batches and is often used in agentmain situations.

The Jar file is similar to the Jar file in the Premain example. It also packages the classes of main and agentmain, TransClass,Transformer and other classes together as "TestInstrument1.jar", while the Manifest file in the Jar file is:

Manifest-Version: 1.0

Agent-Class: AgentMain

In addition, to run Attach API, we can write another control program to simulate the monitoring process: (code snippet)

Import com.sun.tools.attach.VirtualMachine

Import com.sun.tools.attach.VirtualMachineDescriptor

……

/ / A subclass of threads running Attach API

Static class AttachThread extends Thread {

Private final ListlistBefore

Private final String jar

AttachThread (String attachJar, Listvms) {

ListBefore = vms; / / record the VM collection when the program starts

Jar = attachJar

}

Public void run () {

VirtualMachine vm = null

ListlistAfter = null

Try {

Int count = 0

While (true) {

ListAfter = VirtualMachine.list ()

For (VirtualMachineDescriptor vmd: listAfter) {

If (! listBefore.contains (vmd)) {

/ / if there is an increase in VM, we think that the monitored VM has been started

/ / at this point, we began to monitor the 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 ()

}

At run time, you can first run the above main function that starts the new thread, and then, within 5 seconds (simply simulating the monitoring process of JVM), run the following command to start the test Jar file:

Java-javaagent:TestInstrument2.jar-cp TestInstrument2.jar TestMainInJar

If the time is not too bad, the program will first type 1 on the screen, which is the output of the class before the change, and then type some 2, which indicates that agentmain has been successfully attached to JVM by Attach API, the agent program is in effect, and of course, you can see the output of the word "Agent Main Done".

The above example is just a simple example to illustrate this feature. Real examples tend to be complex and may run in multiple JVM in a distributed environment.

What's new in Java SE 6: Instrumentation for local methods

In version 1.5 of instumentation, there is no way to handle Java native methods (Native Method), and under the Java standard JVMTI, there is no way to change method signature, which makes it very difficult to replace native methods. A more straightforward and simple idea is to replace the dynamic link library where the native code is located at startup-- but this is essentially a static replacement rather than a dynamic Instrumentation. Moreover, this may require compiling a large number of dynamic link libraries-for example, we have three local functions, assuming that each needs a replacement, and different combinations may be required for different applications, so if we compile all three functions into the same dynamic link library, we need at most eight different dynamic link libraries to meet our needs. Of course, we can compile it independently, which also requires six dynamic link libraries-in any case, this tedious approach is unacceptable.

In the new features of Java SE 6, the new Native Instrumentation puts forward a new parsing method of native code, which, as a supplement to the original parsing method of native method, solves some problems well. This is why in the new version of the java.lang.instrument package, we have an instrument way to native code-setting prefix.

Suppose we have a native function called nativeMethod, and we need to point it to another function during the run (note that under the current standard JVMTI, all signature needs to be consistent except for the name of the native function). For example, our Java code is:

Package nativeTester

Class nativePrefixTester {

.

Native int nativeMethod (int input)

.

}

So the native code we have implemented is:

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

Now we need to point to another function when we call this function. So according to J2SE's practice, we can add a prefix as the new function name according to his name. For example, if we use "another_" as our prefix, our new function is:

Jint Java_nativeTester_another_nativePrefixTester (jclass thiz, jobject thisObj

Jint input)

It is then compiled into a dynamic link library.

Now that we have a new local function, the next step is to set up instrument. As mentioned above, we can use the premain method to load premain to complete the instrument proxy setting when the virtual machine starts. You can also use agentmain to attach the virtual machine to start the agent. Setting up the native function is also quite simple:

Premain () {/ / or it can also be in agentmain

.

If (! isNativeMethodPrefixSupported ()) {

Return; / / if it cannot be set, return

}

SetNativeMethodPrefix (transformer, "another_"); / / set the prefix of the native function. Note that the underscore must be specified by the user.

.

}

There are two problems to pay attention to here. First, it is not possible to set the prefix of the native function in any case. First, we should notice the features set by Manifest in the agent package:

Can-Set-Native-Method-Prefix

Note that this parameter can affect whether native prefix can be set, and, by default, this parameter is false, we need to set it to true (by the way, the properties in Manifest are case-independent, of course, if you give a value that is not "true", it will be treated as a false value).

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

Second, we can add its own nativeprefix; to each ClassTransformer at the same time, each ClassTransformer can do transform for the same class, so for a Class, a native function may have different prefix, so for this function, it may also have several parsing ways.

In Java SE 6, Native prefix is interpreted as follows: for a native method in a class in a package, first of all, suppose we set the prefix "another" of native to the transformer of this function, which interprets the function interface as:

By the function interface of Java

Native void method ()

And the prefix "another" above, to find the function in the local code

Void Java_package_class_another_method (jclass theClass, jobject thiz)

/ / Please note where prefix appears in the function name!

Once it can be found, call this function and the whole parsing process ends; if not, the virtual machine will do further parsing work. We will use the most basic parsing method of the Java native interface to find the functions in the local code:

Void Java_package_class_method (jclass theClass, jobject thiz)

If found, execute it. Otherwise, the process is declared to fail because there is no suitable parsing method.

So how do you parse if you have multiple transformer and each has its own prefix? In fact, the virtual machine is parsed in the order in which transformer is added to the Instrumentation (remember our most basic addT??? What? Ransformer method? ).

Suppose we have three transformer to be added, and their order and corresponding prefix are: transformer1 and "prefix1_", transformer2 and "prefix2_", transformer3 and "prefix3_". So, the first thing the virtual machine does is parse the interface as:

Native void prefix1_prefix2_prefix3_native_method ()

Then find its corresponding native code.

But if the second transformer (transformer2) does not set prefix, then simply, the resolution we get is:

Native void prefix1_prefix3_native_method ()

This way is simple and natural.

Of course, we also need to pay attention to some complex situations in the case of multiple prefix. For example, suppose we have a native function interface that is:

Native void native_method ()

Then we set up two prefix for it, such as "wrapped_" and "wrapped2_", so what do we get? Yes

Void Java_package_class_wrapped_wrapped2_method (jclass theClass, jobject thiz)

/ / is this function name correct?

Is it? The answer is no, because in fact, there are a series of rules on the mapping of the interface of the native function in Java to native, so there may be some special characters to be substituted. In practice, the correct function name for this function is:

Void Java_package_class_wrapped_1wrapped2_1method (jclass theClass, jobject thiz)

/ / only this function name will be found

Interesting, isn't it? So if we are going to do similar work, a good suggestion is to first write a native interface with prefix in Java, generate a c header-file with the javah tool, and see what the function name it actually parses, so that we can avoid some unnecessary trouble.

Another fact is that, contrary to our imagination, the virtual machine does not do more parsing for two or more prefix; it does not try to remove a prefix and then assemble the function interface. It does and parses only twice.

In short, the prefix-instrumentation approach of the new native changes the disadvantage that the native code in Java cannot be changed dynamically. At present, using JNI to write native code is also a very important part of Java applications, so its dynamic means that the whole Java can be changed dynamically-- now our code can dynamically change the direction of the native function by adding prefix. As mentioned above, if we can't find it, the virtual machine will try to do standard parsing, which gives us a way to replace native code dynamically. We can compile many functions with different prefix into a dynamic link library, and through the function of the instrument package, let the native function change and replace dynamically like the Java function.

Of course, there are some limitations to today's native's instrumentation. For example, different transformer will have its own native prefix, that is, each transformer will be responsible for all the classes it replaces rather than the prefix of a particular class-- so this granularity may not be accurate enough.

What's New in Java SE 6: dynamic additions to BootClassPath / SystemClassPath

We know that by setting system parameters or through virtual machine boot parameters, we can set a virtual machine runtime bootclass load path (- Xbootclasspath) and system class (- cp) load path. Of course, we can't replace it after running it. However, we may sometimes need to load some jar into the bootclasspath, and we cannot apply the above two methods, or we may need to load some jar into the bootclasspath after the virtual machine starts. In Java SE 6, we can do this.

It's easy to do this. First, we still need to make sure that the virtual machine supports this feature, and then add the required classpath to the premain/agantmain. We can use appendToBootstrapC??? in our Transformer What? LassLoaderSearch/appendToSystemClassLoaderSearch to complete this task.

At the same time, we can notice that adding Boot-Class-Path to the manifest of agent can also add its own boot class path while dynamically loading agent. Of course, it can be done more dynamically, conveniently and intelligently in Java code-we can easily add judgment and selection components.

We also need to pay attention to a few points here. First of all, we should not add any system class of the same name related to the system's instrumentation to the jar file of classpath, otherwise, everything will be unpredictable-- that's not what an engineer wants, is it?

Second, we should notice how the ClassLoader of the virtual machine works, which records the parsing results. For example, ClassLoader will remember that we once asked to read a certain class someclass, but failed. Even if we dynamically add a jar containing this class later, ClassLoader will still think that we cannot parse the class and that the same error as the last error will be reported.

Again, we know that there is a system parameter "java.class.path" in the Java language, which records our current classpath, but we use these two functions to really change the actual classpath without having any effect on the property itself.

An interesting thing we can find in the public JavaDoc, Sun designers tell us that this feature actually depends on ClassLoader's appendtoClassPathForInstrumentation method-this is a non-public function, so we do not recommend using it directly (using reflection, etc.). In fact, these two functions in the instrument package can already solve our problems.

We can conclude that in the new features of Java SE 6, the new functions of instrumentation package-- dynamic instrument after virtual machine startup, native code (native code) instrumentation, and dynamic addition of classpath, etc., make Java have stronger dynamic control and interpretation capabilities, thus making the Java language more flexible.

These capabilities, in a sense, begin to change the Java language itself. In the past a long period of time, the emergence and rapid development of dynamic scripting languages has played a very important role in improving the productivity of the whole software industry and network industry. In this context, Java is also slowly making changes. The new features of Instrument and the emergence of the Script platform (which will be introduced in a later article in this series) greatly strengthen the dynamic and integration of language with dynamic language, which is a new trend worth considering in the development of Java.

On how to understand the Instrumentation in Java SE 6 to share here, I hope that the above content can be of some help to you, can learn more knowledge. If you think the article is good, you can share it for more people to see.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report