有些情况下,我们想在当前的 JVM 当中获得一个 Instrumentation 实例。

获取 VM PID

Pre Java 9

代码片段:

String jvmName = ManagementFactory.getRuntimeMXBean().getName();
String jvmPid = jvmName.substring(0, jvmName.indexOf('@'));

完整代码:

import java.lang.management.ManagementFactory;

public class HelloWorld {
    public static void main(String[] args) {
        String jvmName = ManagementFactory.getRuntimeMXBean().getName();
        String jvmPid = jvmName.substring(0, jvmName.indexOf('@'));
        System.out.println(jvmName);
        System.out.println(jvmPid);
    }
}

运行结果:

5452@LenovoWin7
5452

Java 9

In Java 9 the java.lang.ProcessHandle can be used:

long pid = ProcessHandle.current().pid();
import java.lang.management.ManagementFactory;

public class HelloWorld {
    public static void main(String[] args) {
        String jvmName = ManagementFactory.getRuntimeMXBean().getName();
        String jvmPid = jvmName.substring(0, jvmName.indexOf('@'));
        System.out.println(jvmName);
        System.out.println(jvmPid);

        long pid = ProcessHandle.current().pid();
        System.out.println(pid);
    }
}

Output:

9836@LenovoWin7
9836
9836

示例

Application

package sample;

import lsieun.agent.SelfAttachAgent;

import java.lang.instrument.Instrumentation;

public class Program {
    public static void main(String[] args) throws Exception {
        Instrumentation inst = SelfAttachAgent.getInstrumentation();
        System.out.println(inst);
    }
}

SelfAttachAgent

package lsieun.agent;

import com.sun.tools.attach.VirtualMachine;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;
import java.util.jar.Attributes;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;

public class SelfAttachAgent {
    private static volatile Instrumentation globalInstrumentation;

    public static void premain(String agentArgs, Instrumentation inst) {
        globalInstrumentation = inst;
    }

    public static void agentmain(String agentArgs, Instrumentation inst) {
        globalInstrumentation = inst;
    }

    public static Instrumentation getInstrumentation() {
        if (globalInstrumentation == null) {
            loadAgent();
        }
        return globalInstrumentation;
    }

    public static void loadAgent() {
        String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName();
        int index = nameOfRunningVM.indexOf('@');
        String pid = nameOfRunningVM.substring(0, index);

        VirtualMachine vm = null;
        try {
            String jarPath = createTempJarFile().getPath();
            System.out.println(jarPath);

            vm = VirtualMachine.attach(pid);
            vm.loadAgent(jarPath, "");

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (vm != null) {
                try {
                    vm.detach();
                } catch (IOException ignored) {
                }
            }
        }
    }

    public static File createTempJarFile() throws IOException {
        File jar = File.createTempFile("agent", ".jar");
        jar.deleteOnExit();
        createJarFile(jar);
        return jar;
    }

    private static void createJarFile(File jar) throws IOException {
        String className = SelfAttachAgent.class.getName();

        Manifest manifest = new Manifest();
        Attributes attrs = manifest.getMainAttributes();
        attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
        attrs.put(new Attributes.Name("Premain-Class"), className);
        attrs.put(new Attributes.Name("Agent-Class"), className);
        attrs.put(new Attributes.Name("Can-Retransform-Classes"), "true");
        attrs.put(new Attributes.Name("Can-Redefine-Classes"), "true");

        JarOutputStream jos = new JarOutputStream(new FileOutputStream(jar), manifest);
        jos.flush();
        jos.close();
    }
}

Run

Java 8

$ java -cp "${JAVA_HOME}/lib/tools.jar"\;./target/classes/ sample.Program

7704
C:\Users\liusen\AppData\Local\Temp\agent3349937074235412866.jar
sun.instrument.InstrumentationImpl@65b54208

Java 9

$ java -cp ./target/classes/ sample.Program

Caused by: java.io.IOException: Can not attach to current VM

Attach API cannot be used to attach to the current VM by default Link

The implementation of Attach API has changed in JDK 9 to disallow attaching to the current VM by default. This change should have no impact on tools that use the Attach API to attach to a running VM. It may impact libraries that misuse this API as a way to get at the java.lang.instrument API. The system property jdk.attach.allowAttachSelf may be set on the command line to mitigate any compatibility with this change.

Note: since JDK 9 attaching to current process requires setting the system property:

-Djdk.attach.allowAttachSelf=true
$ java -Djdk.attach.allowAttachSelf=true -cp ./target/classes/ sample.Program

Java 9 adds the Launcher-Agent-Class attribute which can be used on executable JAR files to start an agent before the main class is loaded.

总结

本文内容总结如下:

  • 第一点,如何获取当前 VM 的 PID 值。
  • 第二点,在 Java 9 的情况下,默认不允许 attach 到当前 VM,解决方式是将 jdk.attach.allowAttachSelf 属性设置成 true