Введение в Java Agent (3) — принцип и использование JVM Attach

JVM

предыдущийpermainМетод может быть выполнен только до запуска java-программы и не может быть выполнен после запуска программы, но во многих реальных случаях у нас нет возможности установить прокси для виртуальной машины при ее запуске, что фактически ограничивает возможности инструмента. заявление. Эту ситуацию изменили новые возможности Java SE 6. Метод attach в Java Tool API можно использовать для достижения эффекта установки прокси после запуска программы.

API Attach — это не стандартный API Java, а набор расширений API, предоставляемых Sun для «присоединения» агентского инструмента к целевой JVM. С его помощью разработчики могут легко контролировать JVM и запускать дополнительный агент.

API Attach прост, всего 2 основных класса, оба вcom.sun.tools.attachВнутри сумки:VirtualMachineПредставляет виртуальную машину Java, то есть целевую виртуальную машину, которую программа должна отслеживать, обеспечивает перечисление JVM,Attachдействие иDetachДействия (удаление агента из JVM) и т.д.;VirtualMachineDescriptorЭто класс-контейнер, описывающий виртуальную машину.VirtualMachineКлассы выполняют различные функции.

Объедините предыдущую статью и Прикрепите, чтобы узнать, как использовать

В класс Agent добавлено 2 метода agentmain(), параметры которых не используются, приоритет 2-х параметров выше приоритета 1-го параметра, поэтому здесь толькоagentmain (String agentArgs, Instrumentation inst)будет казнен

public class JpAgent {

    public static void premain(String agentArgs){
        System.out.println("我是一个参数的 Java Agent premain");
    }

    public static void agentmain (String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
        inst.addTransformer(new JpClassFileTransformerDemo(), true);
        // retransformClasses 是 Java SE 6 里面的新方法,它跟 redefineClasses 一样,可以批量转换类定义
        inst.retransformClasses(Dog.class);
        System.out.println("我是两个参数的 Java Agent agentmain");

    }
    public static void agentmain (String agentArgs){
        System.out.println("我是一个参数的 Java Agent agentmain");
    }
}

Добавьте новый класс Dog, содержимое следующее, ключевой момент в том, что путь пакета должен быть таким же, как у целевой программы, а остальные не имеют значения

package cn.jpsite.learning;

public class Dog {
}

Создайте новый класс с основной функцией в предыдущем проекте example01.WhileMain.java

public class WhileMain {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(new Dog().say());
        int count = 0;
        while (true) {
            // 等待0.5秒
            Thread.sleep(500);
            count++;
            String say = new Dog().say();
            // 输出内容和次数
            System.out.println(say + count);
            // 内容不对或者次数达到1000次以上输出
            if (!"dog".equals(say) || count >= 1000) {
                System.out.println("有人偷了我的狗!");
                //break;
            }
        }
    }
}

Подготовьте модифицированный Dog.class со следующим содержимым и сохраните его в каталоге отдельно.D:\learning\Dog.class

// 这是一个修改后编译的.class文件,单独存放
public class Dog {
    public String say() {
        return "cat";
    }
}

Что нового в файле resource/META-INF/MANIFEST.MF

Agent-Class: cn.jpsite.learning.javaagent01.JpAgent
Can-Retransform-Classes: true

На этом подготовительная работа в основном завершена, и сборка пакета выполняется.На данный момент, независимо от того, что из следующего выполняется, результата не будет.agentmainвыход

java -javaagent:jpAgent.jar -cp example01-1.0-SNAPSHOT.jar cn.jpsite.learning.Main

java -javaagent:jpAgent.jar -cp example01-1.0-SNAPSHOT.jar cn.jpsite.learning.WhileMain

java -cp example01-1.0-SNAPSHOT.jar cn.jpsite.learning.WhileMain

Результат следующий, и нет необходимости звонитьagentmainМетод, как это сделать?

В настоящее время нам нужно использовать com.sun.tools.attach, чтобы помочь нам достичь настроек прокси после запуска виртуальной машины.Код выглядит следующим образом:

import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;

import java.io.IOException;
import java.util.List;
import java.util.Objects;

/**
 *
 * @author jiangpeng
 * @date 2019/12/1 0001
 */
public class AttachThread extends Thread {
    /**
     * 记录程序启动时的 VM 集合
     */
    private final List<VirtualMachineDescriptor> listBefore;
    /**
     要加载的agent.jar
     */
    private final String jar;

    private AttachThread(String attachJar, List<VirtualMachineDescriptor> vms) {
        listBefore = vms;
        jar = attachJar;
    }

    @Override
    public void run() {
        VirtualMachine vm;
        List<VirtualMachineDescriptor> listAfter;
        int count = 0;

        try {
            while (true) {
                listAfter = VirtualMachine.list();
                vm = hasNewVm(listAfter);

                if(vm == null){
                    System.out.println("没有新jvm程序,请手动指定java pid");
                    try{
                        vm = VirtualMachine.attach("7716");
                    }catch (AttachNotSupportedException e){
                        //System.out.println("拒绝访问 Disconnected from the target VM");
                    }
                }

                Thread.sleep(1000);
                System.out.println(count++);
                if (null != vm || count >= 100) {
                    break;
                }
            }
            Objects.requireNonNull(vm).loadAgent(jar);
            vm.detach();
        } catch (Exception e) {
            System.out.println("异常:" + e);
        }
    }

    /**
     * 判断是否有新的 JVM 程序运行
     */
    private VirtualMachine hasNewVm(List<VirtualMachineDescriptor> listAfter) throws IOException, AttachNotSupportedException {
        for (VirtualMachineDescriptor vmd : listAfter) {
            if (!listBefore.contains(vmd)) {
                // 如果 VM 有增加,,我们开始监控这个 VM
                System.out.println("有新的 vm 程序:"+ vmd.displayName());
                return VirtualMachine.attach(vmd);
            }
        }
        return null;
    }

    public static void main(String[] args)  {
        new AttachThread("D:/learning/jpAgent.jar", VirtualMachine.list()).start();
    }
}

вwhileЦиклическая часть получает набор java-процессов каждую 1 секунду. Если нет, будет предложено вручную указать java-программу для выполнения.attach, если зациклиться 100 раз или получитьVirtualMachine, затем выйдитеwhile(true)для загрузки указанногоagent.jar.

2 метода тестирования

выполнить первымjava -cp example01-1.0-SNAPSHOT.jar cn.jpsite.learning.WhileMainЗапустите программу example01 и увидите следующие результаты. Помните, что мы подготовили Dog.class ранее. Содержимое say() в нем — cat. Позже вы увидите волшебную сцену.

Затем запустите AttachThread.java прямо сейчас и увидите следующее.

использоватьjps -lКоманда для просмотра номера порта всех запущенных java-программ

Измените AttachThread.java вVirtualMachine.attach("7716");кодVirtualMachine.attach("16304");16304 — идентификатор java-процесса WhileMain на приведенном выше рисунке, перезапустите AttachThread.java, результат будет следующим.

loader className: cn/jpsite/learning/Dog

Я агент агента Java с двумя аргументами

Видно, что agentmain был выполнен, и содержание dog в исходном методе Dog.say() также было изменено на cat

второй

Сначала запустите AttachThread.java, затем выполнитеjava -cp example01-1.0-SNAPSHOT.jar cn.jpsite.learning.WhileMain, вы можете быстро увидеть, что результат изменился

Дополнительные свойства MANIFEST.MF

Can-Set-Native-Method-Prefix
System-Class-Path
Boot-Class-Path

Новое в Java SE 6: динамическое добавление BootClassPath/SystemClassPath

Обратите внимание на несколько моментов. Во-первых, в файле jar, который мы добавляем в classpath, не должно быть ни одного класса с таким же именем, как у системы, относящегося к инструментированию системы, иначе все будет непредсказуемо

Следует обратить внимание на то, как работает ClassLoader виртуальной машины, он будет записывать результат парсинга. Например, однажды мы попросили прочитать класс someclass, но это не удалось, ClassLoader это запомнит. Даже если мы позже динамически добавим банку, содержащую этот класс, ClassLoader все равно будет думать, что мы не можем разрешить этот класс, и будет сообщено о той же ошибке, что и последняя ошибка.

Опять же, мы знаем, что в языке Java есть системный параметр "java.class.path". Это свойство записывает наш текущий путь к классам. Тем не менее, мы используем эти две функции, хотя фактический путь к классам действительно изменен, он не будет иметь никакого значения. воздействие на само имущество.

Обратите внимание, не потеряйтесь

Статья постоянно обновляется каждую неделю, вы можете найти «Десять минут на изучение программирования» на WeChat, чтобы прочитать и обновить ее как можно скорее.Если эта статья хорошо написана, если вы чувствуете, что есть что-то, чего можно желать ~ ставьте лайк 👍 подписывайтесь ❤️ поделитесь ❤️
Ваша поддержка и признание — самая большая мотивация для моего творчества, увидимся в следующей статье!