Java Instrument application: dynamically modify the Class definition in operation

 

Provides services that allow Java programming language agents to instrument programs running on the JVM. The mechanism for instrumentation is modification of the byte-codes of methods. [used to allow the Java programming language agent to detect the service provided by the program running on the JVM. The detection mechanism is to modify the bytecode of the method.]

This is Java Description of lang.instrument package. With Instrumentation, developers can build an application independent Agent to monitor and assist the programs running on the JVM, and even replace and modify the definitions of some classes.

If you haven't started or want more knowledge, you can check it out

Why did I study this? At the beginning, it was based on such a demand: an ancient project is dedicated to live APP activities. Each activity has a corresponding life cycle. In addition, it is not modularized (I am also considering how to do it). After a long time, most activities have been offline, and only a few activities are still in operation, Just think about how to detect which code will still be executed for migration. Having thought about timing jstack or Spring AOP, I found that they are not suitable. Fortunately, I have learned this knowledge before and think it can be used. We can use the Java Instrument to solve the following problems:

  1. For online positioning, you want to know the value of a variable when it is executed (IDEA remote Debug?): Temporarily add the value of the logging variable
  2. For performance optimization, you need to know the time-consuming of executing each piece of code online: temporarily add logs to record the time-consuming of code execution
  3. Assist the Tester to test scenarios where data cannot be created (such as the logic of a specific date and time): temporarily write the value of a variable

The above all involve the same requirement: temporarily modify the method body. At this time, we can use redefiniteclasses of Instrumentation.

1. Develop agent class

package cn.zhh;

import java.io.IOException;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.nio.file.Files;
import java.nio.file.Paths;

/**
 * Agent-Class
 */
public class AgentMain {

    /**
     * Running agent entry
     *
     * @param agentArgs Custom parameters
     * @param inst      Enhancement class
     * @throws Exception abnormal
     */
    public static void agentmain(String agentArgs, Instrumentation inst) throws Exception {
        // User defined parameters are separated by commas: [0] - absolute path of class file, [1] - full name of class
        String[] args = agentArgs.split(",");
        // Redefine Class
        inst.redefineClasses(new ClassDefinition(Class.forName(args[1]), readClassBytes(args[0])));
    }

    /**
     * Read file contents
     *
     * @param classPath File path
     * @return File byte data
     * @throws IOException File reading exception
     */
    private static byte[] readClassBytes(String classPath) throws IOException {
        return Files.readAllBytes(Paths.get(classPath));
    }
}

2. Develop the main function of executable jar package

The executable jar package can be separated from the proxy jar package, which is more convenient to put together. Need to rely on tools Jar compilation (provided by JDK, just import {JDK root directory} / lib/tools.jar). Because interface programming is used, it is not necessary to distinguish the JDK of the platform.

package cn.zhh;

import com.sun.tools.attach.VirtualMachine;

import java.util.Arrays;
import java.util.Objects;

/**
 * mainClass
 */
public class Main {

    /**
     * Executable jar package main function
     *
     * @param args Custom function
     * @throws Exception abnormal
     */
    public static void main(String[] args) throws Exception {
        if (Objects.isNull(args) || args.length != 4) {
            throw new RuntimeException("Incorrect number of parameters, 4 required: first agent Package absolute path, second Java process PID,Third class File absolute path, Fourth class full name");
        }
        System.out.println("Main run, args are:");
        Arrays.stream(args).forEach(System.out::println);
        VirtualMachine virtualMachine = VirtualMachine.attach(args[1]);
        try {
            virtualMachine.loadAgent(args[0], args[2] + "," + args[3]);
        } finally {
            virtualMachine.detach();
        }
    }
}

3. Pack

requirement:

  1. Tools for specific platforms (Windows, Linux, Mac, etc.) are required for runtime jar. So either put the dependency into the jar package or add the class library path when executing Java jar.
  2. Generate corresponding manifest MF list.

Therefore, Maven is recommended

1) Add tools Jar dependency

Install the jar package to the local warehouse (do not use Library dependency or systemPath dependency): MVN install: install file - dgroupid = com sun -DartifactId=tools -Dversion=1.8 -Dpackaging=jar -Dfile=D:\Java\jdk1. 8.0_ 141\lib\tools. jar

<dependency>
    <groupId>com.sun</groupId>
    <artifactId>tools</artifactId>
    <version>1.8</version>
</dependency>

2) Add the assembly plug-in and the configuration list

            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>cn.zhh.Main</mainClass>
                        </manifest>
                        <manifestEntries>
                            <Agent-Class>
                                cn.zhh.AgentMain
                            </Agent-Class>
                            <Can-Redefine-Classes>
                                true
                            </Can-Redefine-Classes>
                        </manifestEntries>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>

3) Run the assembly command to get the executable jar package containing dependencies

 

4. Use

1) Write a target program and run it

2) Use the jps command to view the Java process pid: 12780

3) Modify the Task class, recompile it, and rename the bytecode file to Task-1 class

4) The ultimate operation is to run the jar package and witness the miracle

java -jar agent-1.0-jar-with-dependencies.jar D:\IdeaProjects\java-agent\agent\target\agent-1.0-jar-with-dependencies.jar 12780 D:\IdeaProjects\java-agent\target\target\classes\cn\zhh\Task-1.class cn.zhh.Task

console output

Target program console output

Needless to say? Applause! Sprinkle flowers!

 

Added by mjm7867 on Sun, 20 Feb 2022 12:45:00 +0200