Реализация агента на основе инструментов Java

Java задняя часть JVM API

0 Введение

Использование инструментов,Позволяет разработчикам создавать независимый от приложения агент (агент) для мониторинга и помощи программам, работающим на JVM, и даже заменять и изменять определения определенных классов.. С помощью такой функции разработчики могут реализовать более гибкий мониторинг виртуальных машин во время выполнения и операции класса Java.Реализация АОП, поддерживаемая на уровне виртуальной машины., чтобы разработчики могли реализовать некоторые функции АОП без каких-либо обновлений и изменений JDK.

Использование java.lang.instrument для статического инструментирования — это новая функция Java SE 5.Он освобождает инструментальную функцию Java от собственного кода, позволяя решать проблемы в коде Java..

В Java SE 6 инструментальный пакет получил больше возможностей:Инструменты после запуска, инструменты собственного кода и динамически изменяющиеся пути к классами Т. Д. Эти изменения означают, чтоJava обладает более сильным динамическим контролем и возможностями интерпретации, что делает язык Java более гибким и изменчивым..

  1. В Java SE6 самое большое изменение включает инструментирование во время выполнения.. В Java SE 5,Инструмент требует, чтобы прокси-класс был установлен с параметрами командной строки или системными параметрами перед запуском., в реальной эксплуатацииКогда виртуальная машина инициализируется (до загрузки подавляющего большинства библиотек классов Java), запускаются настройки инструментовки, и в виртуальной машине устанавливается функция обратного вызова для обнаружения загрузки определенного класса и завершения фактической работы. Но во многих практических случаях у нас нет возможности установить прокси для виртуальной машины при ее запуске, что фактически ограничивает применение инструмента.. Новые функции Java SE 6 меняют это,С помощью метода attach в Java Tool API, мы можем легкоДинамически устанавливайте класс прокси-сервера загрузки во время работы для достижения цели инструментирования..

  2. Кроме того, Instrumentation for native — это совершенно новая функция Java SE 6, которая делает это невозможным раньше —Инструментарий для собственного интерфейса может быть выполнен в Java SE 6 с одним или рядом префиксов..

  3. Наконец,Инструментарий в Java SE 6 также добавляет возможность динамически добавлять пути к классам.. Все эти новые функции делают инструментальный пакет еще более многофункциональным, делая сам язык Java еще более мощным.

1 Основные функции и использование

Фактически реализация пакета java.lang.instrument основана на механизме JVMTI:В реализации Instrumentation есть агентская программа JVMTI, которая завершает динамическую работу классов Java, вызывая функции, связанные с классами Java в JVMTI..除开 Instrumentation 功能外,JVMTI также предоставляет большое количество полезных функций для управления памятью виртуальной машины, управления потоками, манипулирования методами и переменными и т. д..

JVMTI (Java Virtual Machine Tool Interface) — это набор собственных программных интерфейсов, предоставляемых виртуальной машиной Java для инструментов, связанных с JVM. JVMTI был представлен в Java SE 5, интегрируя и заменяя ранее использовавшийся интерфейс профилировщика виртуальной машины Java (JVMPI) и интерфейс отладки виртуальной машины Java (JVMDI), в то время как в Java SE 6 JVMPI и JVMDI исчезли.JVMTI предоставляет набор «агентских» программных механизмов, которые могут поддерживать сторонние инструментальные программы для подключения и доступа к JVM через прокси, а также использовать богатый программный интерфейс, предоставляемый JVMTI, для выполнения многих функций, связанных с JVM..

1.1 Статический инструмент перед запуском JVM

Основная роль Инструмента заключается в том, что определения классов динамически изменяются и операции. В Java SE 5 и более поздних версиях разработчики могут запускать обычную программу Java (класс Java с основной функцией),Запустите агент Instrumentation, указав конкретный файл jar (содержащий агент Instrumentation) с параметром -javaagent..

  1. Напишите предглавную функцию

    Напишите класс Java, который содержит любой из следующих двух методов.:

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

    в,[1], чем приоритет [2] более высокий приоритет будет выполнен ([1] и [2] существуют, [2] игнорируется).在这个 premain 函数中,开发者可以进行对类的各种操作。

    1. agentArgs — параметр программы, полученный функцией premain, переданный с помощью "-javaagent". В отличие от основной функции,Этот параметр является строкой, а не массивом строк.Если параметров программы несколько, программа сама разрешает эту строку.
    2. Inst — это экземпляр java.lang.instrument.Instrumentation, который автоматически передается JVM.. java.lang.instrument.Instrumentation 是 instrument 包中定义的一个接口,也是这个包的核心部分,Централизованы почти все функциональные методы, такие как преобразования и операции, определяемые классом, и т. д.
  2. упаковка файла jar

    Упакуйте этот класс Java в файл jar,И добавьте «Premain-Class» в свойство META-INF/MAINIFEST.MF, чтобы указать класс Java с premain, написанным на шаге 1.. (может также потребоваться указать другие свойства, чтобы включить дополнительные функции)

  3. бегать

    Запустите программу Java с инструментами следующим образом:

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

Например, замените файл класса Java каштановым: используйте простой метод замены файла класса, чтобы продемонстрировать использование инструментария.

Операцию файла класса Java можно понимать как операцию массива байтов (чтение двоичного потока байтов файла класса в массив байтов).Разработчики могут получать, манипулировать и, наконец, возвращать определение класса (массив байтов) в методе преобразования «ClassFileTransformer»..

  1. Во-первых, у нас есть простой класс TransClass, который возвращает целое число 1 с помощью статического метода.

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

    Мы запускаем следующий класс и получаем вывод «1»:

    public class TestMainInJar { 
      public static void main(String[] args) { 
         System.out.println(new TransClass().getNumber()); 
      } 
    }
    
  2. Затем мы меняем метод getNumber класса TransClass на следующий:

    public int getNumber() { 
        return 2; 
    }
    

    Затем скомпилируйте файл Java, который возвращает 2, в файл класса, чтобы отличить исходный класс, который возвращает 1,Мы назовем этот файл класса, который возвращает 2 TransClass2.class.2.

  3. Далее мы создаем класс трансформатора: этот класс реализует интерфейс ClassfileTransformer

    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; 
    
    public 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; 
            } 
        } 
       /**
        * 参数: 
        * loader - 定义要转换的类加载器;如果是引导加载器,则为 null 
        * className - 完全限定类内部形式的类名称和 The Java Virtual Machine Specification 中定义的接口名称。例如,"java/util/List"。 
        * classBeingRedefined - 如果是被重定义或重转换触发,则为重定义或重转换的类;如果是类加载,则为 null 
        * protectionDomain - 要定义或重定义的类的保护域 
        * classfileBuffer - 类文件格式的输入字节缓冲区(不得修改) 
        * 返回: 
        * 一个格式良好的类文件缓冲区(转换的结果),如果未执行转换,则返回 null。 
        * 抛出: 
        * IllegalClassFormatException - 如果输入不表示一个格式良好的类文件 
        */
        public byte[] transform(ClassLoader l, String className, Class<?> c, 
             ProtectionDomain pd, byte[] b) throws IllegalClassFormatException { 
            if (!className.equals("TransClass")) { 
                return null; 
            } 
            return getBytesFromFile(classNumberReturns2); 
        } 
    }
    

    Среди них метод getBytesFromFile считывает поток двоичных символов в соответствии с именем файла, а метод преобразования, указанный в ClassFileTransformer, завершает замену и преобразование определения класса..

  4. Наконец, мы создаем класс Premain и пишем прокси-метод Instrumentation premain:

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

    Видно, что метод addTransformer не указывает, какой класс нужно преобразовать.Преобразование происходит после выполнения предварительной функции и перед выполнением основной функции.В это время каждый раз, когда класс загружается, метод преобразования будет выполняться один раз, чтобы увидеть, нужно ли его преобразовать., поэтому в методе преобразования (класс Transformer) программа использует className.equals("TransClass"), чтобы определить, нужно ли преобразовывать текущий класс.

  5. После завершения кода мы упаковываем их как Testinstrument1.jar. Класс для Transclass, который возвращает 1, содержится в пакете JAR, а TransClass.class.2, который возвращает 2 размещен за пределами банки. Добавьте следующий атрибут в манифест, чтобы указать класс, где находится Premain:

    Manifest-Version: 1.0 
    Premain-Class: Premain
    
  6. При запуске этой программы, если мы запустим основную функцию в этой банке обычным способом, мы можем получить вывод «1». Если запустить с:

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

    Вы получите вывод «2».

    Конечно,Основная функция, которую запускает программа, не обязательно помещать в файл jar, где находится premain., здесь просто собрал для удобства пример упаковки программы.В дополнение к использованию 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 является более мощным и может конвертировать множество классов в пакетном режиме.

多个代理可以同时执行,按照代理指定的顺序被依次调用

1.2 Динамический инструмент после запуска JVM

В Java SE 5 разработчики могут использовать свое воображение только в premain,Инструментарий также ограничивается основной функцией перед выполнением, этот метод имеет определенные ограничения.

На основе Java SE 5 в Java SE 6 внесены улучшения для этой ситуации,Разработчики могут запускать свои собственные инструментальные программы после того, как основная функция начнет выполняться..

В инструментарии Java SE 6,Существует метод «agentmain», который находится «бок о бок» с premain, который можно запустить после того, как основная функция запустится.. Как и в случае с функцией premain, разработчики могут написать класс Java, содержащий функцию «agentmain»:

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

такой же,[1] имеет более высокий приоритет, чем [2], и будет выполняться первым ([2] игнорируется, если [1] и [2] существуют одновременно). Как и в случае с функцией premain, разработчики могут выполнять различные операции над классом в agentmain. Использование agentArgs и Inst такое же, как и premain.

И «Premain-Class». Точно так же разработчик должен установить «Agent-Class», указанный в файле манифеста, который содержит функцию класса agentmain.

Однако, в отличие от premain, agentmain необходимо запустить после того, как запустится основная функция.Как определить такой тайминг и как реализовать такую ​​функцию?

В документации по Java SE 6 разработчики могут не увидеть четкого введения в разделе документации, относящегося к пакету java.lang.instrument, не говоря уже о конкретном примере применения agnetmain. Однако в новых функциях Java SE 6 есть незаметное место, раскрывающее использование agentmain.Это API-интерфейс Attach, предоставляемый в Java SE 6..

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

API Attach прост, всего 2 основных класса, оба в пакете com.sun.tools.attach:

  1. VirtualMachine представляет собой виртуальную машину Java., то есть целевая виртуальная машина, которую программа должна отслеживать, обеспечивает перечисление JVM,Действие «Присоединить» и «Отключить» (противоположное действию «Присоединить», которое удаляет агент из JVM) и т. д. ;

    Класс VirtualMachine, этот класс позволяет намПодключитесь к jvm удаленно, передав pid jvm (идентификатор процесса) в метод присоединения. Тогда мы можемЗарегистрируйте агент программы агента с помощью jvm через метод loadAgent, и экземпляр Instrumentation будет получен в программе агента агента, экземпляр может бытьИзмените байт-код класса перед загрузкой класса или перезагрузите его после загрузки класса.. При вызове методов экземпляра Instrumentation эти методы обрабатываются с помощью методов, предоставленных в интерфейсе ClassFileTransformer.

  2. VirtualMachinedescriptor - это описание класса контейнеров виртуальной машины, с классом VirtualMachine для выполнения различных функций.

Для простоты упростим пример следующим образом: по-прежнему использовать метод замены файла класса

  1. Замените функцию, возвращающую 1, на функцию, возвращающую 2. API Attach написан в виде потока и использует ожидание ожидания.Проверять все виртуальные машины Java каждые полсекунды, когда будет найдена новая виртуальная машина,Просто вызовите функцию прикрепления, а затем загрузите файл Jar, как описано в документации по API прикрепления.. Через 5 секунд программа подключения завершается автоматически.
  2. Внутри основной функции программа выводит возвращаемое значение каждые полсекунды (показывая, что возвращаемое значение изменяется с 1 на 2).

Код классов TransClass и Transformer остается без изменений, см. введение в предыдущем разделе. Код TestMainInJar с основной функцией:

// 程序每隔半秒钟输出一次返回值(显示出返回值从 1 变成 2)
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 с 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"); 
    } 
}

в,retransformClasses — это новый метод в Java SE 6. Как и redefineClasses, он может преобразовывать определения классов в пакетном режиме и в основном используется в ситуациях с главным агентом..

JAR-файл аналогичен файлу JAR в помещении. Он также выкладывает класс Main и AgentMain, транскласса, трансформатора и т. Д., Упакованный как «TestInstrument1.jar», а файл манифеста в файле JAR является:

Manifest-Version: 1.0 
Agent-Class: AgentMain

Кроме того, для запуска Attach API мы можем написать еще одну управляющую программу для имитации процесса мониторинга:

import com.sun.tools.attach.VirtualMachine; 
import com.sun.tools.attach.VirtualMachineDescriptor; 
……
// 一个运行 Attach API 的线程子类
// 每隔半秒时间检查一次所有的 Java 虚拟机
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(); 
}

Время работы,Сначала запустите вышеуказанную основную функцию, которая запускает новую тему, а затем в течение 5 секунд (просто смоделируйте процесс мониторинга JVM) выполните следующую команду, чтобы запустить тестовый файл Jar:

java –cp TestInstrument1.jar TestMainInJar

Если время не слишком плохое, программа сначала выведет на экран 1, что является выводом класса до изменения, а затем напечатает 2,Это указывает на то, что agentmain был успешно подключен к JVM с помощью API Attach, и агент вступил в силу., и, конечно же, вы также можете увидеть вывод слов «Главный агент готово».

1.3 Инструмент собственного метода

В версии инструментария JDK 1.5 нет обработки собственных методов Java (Native Method), а в соответствии со стандартом Java JVMTI нет возможности изменить сигнатуру метода, что очень затрудняет замену собственных методов.Относительно простая и простая идея состоит в том, чтобы заменить библиотеку динамической ссылки, где нативный код находится при запуске- но так,По сути, статическая замена, а не динамическая инструментация..

Также для этого может потребоваться компиляция большего количества DLL - например, у нас есть три нативные функции, предполагая, что каждая требует замены, и под разные приложения могут требоваться разные комбинации, то если мы поместим три каждая функция компилируется в одна и та же библиотека динамической компоновки, нам нужно не более 8 различных библиотек динамической компоновки для удовлетворения потребностей. Конечно, мы можем скомпилировать его и самостоятельно, для чего также потребуется 6 динамически подключаемых библиотек — в любом случае такой громоздкий способ неприемлем.

В Java SE 6 новый Native Instrumentation предлагает новый собственный метод синтаксического анализа кода в качестве дополнения к исходному методу синтаксического анализа собственного метода для хорошего решения некоторых проблем. Это в новой версии пакета java.lang.instrument,У нас есть инструментарий для нативного кода — установите префикс.

Предположим, у нас есть нативная функция с именем nativeMethod,Во время работы нам нужно указать его на другую функцию (следует отметить, что при текущем стандарте JVMTI, помимо нативного имени функции, должны быть согласованы и другие подписи). Например, наш Java-код:

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

Тогда нативный код, который мы реализовали:

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

Теперь нам нужно, чтобы эта функция указывала на другую функцию, когда мы ее вызываем. Затем, согласно подходу J2SE,Мы можем добавить префикс в качестве имени новой функции в соответствии с его методом именования. Например, мы ставим префикс «другой_».Тогда наши новые функции:

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

После этого вы будете включены в динамически подключаемую библиотеку.Теперь, когда у нас есть новая локальная функция, следующим шагом будет установка инструмента.. Как указано выше,Мы можем использовать метод premain для загрузки premain, когда виртуальная машина начинает завершать настройку прокси-сервера инструмента. Вы также можете использовать метод agentmain, чтобы подключить виртуальную машину для запуска агента.. А настроить нативную функцию довольно просто:

premain(){  // 或者也可以在 agentmain 里
    …
    if (!isNativeMethodPrefixSupported()){ 
        return; // 如果无法设置,则返回
    } 
    setNativeMethodPrefix(transformer,"another_"); // 设置 native 函数的 prefix,注意这个下划线必须由用户自己规定
    …
}

Здесь следует отметить два вопроса:

  1. Установить префикс нативной функции можно не во всех случаях, в первую очередь следует обратить внимание на характеристики, установленные Манифестом в пакете агента:

    Can-Set-Native-Method-Prefix
    

    обращать внимание,Этот параметр может влиять на возможность установки собственного префикса., и, в настройке по умолчанию, этот параметр имеет значение false, нам нужно установить его в значение true (кстати, свойства в Манифесте нечувствительны к регистру, конечно, если вы дадите значение, не равное "" true" значение, оно будет рассматриваться как ложное значение).

    Конечно, нам тоже нужноУбедитесь, что сама виртуальная машина поддерживает setNativePrefix.. В API Java,Класс Instrumentation предоставляет функцию isNativePrefix, с помощью которой мы можем узнать, может ли функция быть реализована..

  2. Мы можем добавить свой собственный префикс к каждому ClassTransformer:

    Каждый ClassTransformer может преобразовать один и тот же класс, поэтому для класса нативная функция может иметь разные префиксы, поэтому для этой функции также может быть несколько способов разбора. В Java SE 6 собственные префиксы интерпретируются следующим образом:Для нативного метода в классе в пакете, во-первых, предположим, что мы установили нативный префикс «another» для преобразователя этой функции, который интерпретирует интерфейс функции как :

    По функциональному интерфейсу Java:

    native void method()
    

    И сказал префикс «другой», функция для поиска местного кода:

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

    После того, как можно найти, затем вызовите эту функцию, весь процесс разрешения окончен;Если не найдено, виртуальная машина выполнит дальнейшую работу по синтаксическому анализу. Мы будем использовать самый простой метод синтаксического анализа нативного интерфейса Java, чтобы найти функции в нативном коде. :

    void Java_package_class_method(jclass theClass, jobject thiz);
    

    Если найдено, выполните его. В противном случае из-за отсутствия подходящего метода синтаксического анализа процесс объявляется неудачным. Итак, если трансформеров несколько, и у каждого свой префикс, как его разобрать?На самом деле виртуальная машина анализируется в том порядке, в котором преобразователь был добавлен в Instrumentation (помните наш самый простой метод addTransformer?). Предположим, у нас есть три преобразователя, которые нужно добавить, их порядок и соответствующий префикс: трансформатор1 и «префикс1_», трансформатор2 и «префикс2_», трансформатор3 и «префикс3_». Что ж, первое, что делает виртуальная машина, — это разрешает интерфейс:

    native void prefix1_prefix2_prefix3_native_method()
    

    Затем перейдите к соответствующему собственному коду. А вот если у второго преобразователя (transformer2) не установлен префикс, то очень просто, парсинг у нас получается такой:

    native void prefix1_prefix3_native_method()
    

    Этот метод прост и натуральный. Конечно, для случая нескольких префиксов нам также нужно обратить внимание на некоторые сложные ситуации. Например, предположим, что у нас есть внутренний интерфейс функций:

    native void native_method()
    

    Затем мы устанавливаем для него два префикса, например, «wrapped_» и «wrapped2_», и что мы получаем?

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

    ответ отрицательный,Потому что на самом деле существует ряд правил для сопоставления интерфейса нативной функции в Java с нативным, поэтому могут быть некоторые специальные символы, которые нужно заменить в. На практике правильное имя функции для этой функции:

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

    Это весело, не так ли? Итак, если бы мы сделали что-то вродеХорошим предложением является сначала написать собственный интерфейс с префиксом на Java, использовать инструмент javah для создания файла заголовка c и посмотреть, что на самом деле анализирует имя функции, чтобы мы могли избежать некоторых ненужных проблем..

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

    Короче,Новый метод нативной префиксной аппаратуры устранил тот недостаток, что нативный код в Java нельзя было динамически изменять.. В настоящее время,Использование JNI для написания собственного кода также является очень важной частью приложений Java., поэтому его динамизация означает, что вся Java может быть изменена динамически —Теперь наш код может динамически изменять указатель нативной функции, добавляя префикс.

    Как упоминалось выше, если он не будет найден, виртуальная машина также попытается выполнить стандартный синтаксический анализ, что позволит нам динамически заменить нативный код, мы можем скомпилировать множество функций с разными префиксами в динамической библиотеке среди,А с помощью функции пакета инструментов родная функция может динамически изменяться и заменяться, как функция Java.. Конечно, текущая нативная аппаратура имеет некоторые ограничения, например, разные преобразователи будут иметь свои нативные префиксы, т. е.Каждый преобразователь будет отвечать за все классы, которые он заменяет, а не за конкретный префикс класса.- так что эта детализация может быть недостаточно точной.

1.4 Динамическое добавление BootClassPath/SystemClassPath

Мы знаем, что задав системные параметры или через параметры запуска виртуальной машины, мы можемУстановите путь загрузки класса загрузки (-Xbootclasspath) и путь загрузки системного класса (-cp) при работе виртуальной машины.. Конечно, мы не можем заменить его после запуска. Однако иногда нам может понадобиться загрузить некоторые jar-файлы в bootclasspath, и мы не можем применить два вышеуказанных метода;Или нам нужно загрузить несколько jar-файлов в bootclasspath после запуска виртуальной машины.. В Java SE 6 мы можем это сделать.

Достичь этих точек очень просто, в первую очередь нам еще понадобитсяУбедитесь, что виртуальная машина уже поддерживает эту функцию., а затем добавьте требуемый путь к классам в файл premain/agantmain.Мы можем использовать appendToBootstrapClassLoaderSearch/appendToSystemClassLoaderSearch в Instrumentation inst в методе premain/agentmain для выполнения этой задачи..

В то же время мы можем заметить, чтоПрисоединяйтесь к Boot-Class-Path в агенте mainfest, на самом деле один и тот же агент может динамически загружать свой путь к классу загрузки., конечно, это можно сделать более динамично и разумно в коде Java — мы можем легко добавить компоненты оценки и выбора.

Здесь также необходимо обратить внимание на несколько моментов:

  1. Во-первых,Файл jar, который мы добавляем в путь к классам, не должен иметь класс с тем же именем, что и инструментарий системы., иначе все непредсказуемо - не этого хочет инженер, не так ли?

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

  3. Опять же, мы знаем, что в языке Java есть системный параметр «java.class.path», это свойство записывает наш текущий путь к классам, однако мы используем эти две функции,При фактическом изменении фактического пути к классам это не влияет на само свойство..

В общедоступном JavaDoc мы можем найти очень интересную вещь, как нам рассказали дизайнеры Sun,Эта функция на самом деле зависит от метода ClassLoader appendtoClassPathForInstrumentation - это непубличная функция, мы не рекомендуем использовать ее напрямую (с использованием отражения и т. д.), на самом деле сумка с инструментами имеет две функции, которые могут быть очень хорошим решением. наши проблемы.

1.5 Список META-INF/MAINIFEST.MF

Ниже приведен список атрибутов манифеста jar-файла агента:

  1. Premain-ClassЕсли при запуске JVM был указан прокси, это свойство указывает класс прокси,то есть класс, который содержит метод premain. Это свойство является обязательным, если агент был указан при запуске JVM. Если свойство не существует, JVM прервется.Примечание. Это свойство является именем класса, а не именем файла или путем..
  2. Agent-ClassЕсли реализация поддерживает механизм запуска агента в какой-то момент после запуска виртуальной машины, то этот атрибут указывает класс агента,то есть класс, который содержит метод agentmain. Это свойство является обязательным, если его нет, агент не запустится.Примечание: это имя класса, а не имя файла или путь.
  3. Boot-Class-PathУстановите список пути поиска погрузчика загрузки класса. Путь представляет каталог или библиотеку (обычно цитируется на многих платформах как баночки или библиотеки zip).Найдите определенные классовые механизмы платформы после сбоя загрузчика класса Bootstrap, будут искать эти пути. Пути поиска в указанном порядке. Пути в списке разделены одним или несколькими пробелами. Пути используют синтаксис компонента пути иерархических URI. Если путь начинается с символа косой черты ("/"), это абсолютный путь, в противном случае — относительный. Относительные пути разрешаются относительно абсолютного пути к прокси-файлу JAR. Неверные пути и несуществующие пути игнорируются. Пути, не представляющие файл JAR, игнорируются, если агент запускается через некоторое время после запуска виртуальной машины. Это свойство является необязательным.
  4. Can-Redefine-ClassesBoolean (истина или ложь, регистр не зависит).Можно ли переопределить классы, требуемые этим прокси. Значения, отличные от true, считаются ложными. Это свойство является необязательным и по умолчанию имеет значение false.
  5. Can-Retransform-ClassesBoolean (истина или ложь, регистр не зависит).Можно ли переделать классы, требуемые этим прокси. Значения, отличные от true, считаются ложными. Это свойство является необязательным и по умолчанию имеет значение false.
  6. Can-Set-Native-Method-PrefixBoolean (истина или ложь, регистр не зависит).Можно ли установить собственный префикс метода, необходимый для этого прокси?. Значения, отличные от true, считаются ложными. Это свойство является необязательным и по умолчанию имеет значение false.

2 Инструмент двухъядерных API

  1. ClassFileTransformer:Предопределенный загрузчик классов, вы можете выполнить некоторую обработку байт-кода класса, который будет загружен в этот класс, например, улучшить байт-код;
  2. Instrumentation: Enhancer, переданный нам JVM в параметре входа, предоставляет следующие функции:
    1. addTransformer/removeTransformer: зарегистрировать/удалить ClassFileTransformer;
    2. retransformClasses:Повторно преобразовать загруженный класс, что вызовет повторную загрузку определения класса., следует отметить, что вновь загруженный класс не может изменять объявление старого класса, например добавлять атрибуты и изменять объявления методов;
    3. redefineClasses: аналогично предыдущему,но не повторять процесс преобразования, а напрямую отправить результат обработки (байт-код) непосредственно в JVM;
    4. getAllLoadedClasses:Получить текущий загруженный класс, можно использовать с retransformClasses;
    5. getInitiatedClasses:Получите определение класса, загруженное определенным классовым загрузчиком;
    6. getObjectSize:Получить пространство, занимаемое объектом, Включая его ссылки на объекты;
    7. appendToBootstrapClassLoaderSearch/appendToSystemClassLoaderSearch: увеличить путь поиска BootstrapClassLoader/SystemClassLoader;
    8. isNativeMethodPrefixSupported/setNativeMethodPrefix:Определите, поддерживает ли JVM перехват собственного метода.;