Научит вас, как реализовать Java-агент со статистикой, отнимающей много времени.
Впереди два предвещающих сообщения в блоге, в сообщении в блоге«200303-Как элегантно подсчитать трудоемкие блоки кода в Java», в котором, наконец, упоминается использование java-агента для подсчета трудоемкого метода.
Сообщение блога«200316-IDEA + проект Java-агента с нулевой базой maven»В подробном описании теста настройте проект разработки java-агента всего процесса.
Этот пост в блоге вступит в настоящую битву с Java-агентом и научит вас, как реализовать трудоемкий Java-агент с помощью статистических методов.
1. Основные точки осанки
Несмотря на то, что два предыдущих раздела учат вас реализовывать версию агента hello world, на самом деле java-агент все еще находится в затруднительном положении, поэтому сначала нам нужно пополнить базовые знания.
Сначала посмотрите на параметры в двух методах агентаInstrumentation
, давайте посмотрим на его определение интерфейса
/**
* 注册一个Transformer,从此之后的类加载都会被Transformer拦截。
* Transformer可以直接对类的字节码byte[]进行修改
*/
void addTransformer(ClassFileTransformer transformer);
/**
* 对JVM已经加载的类重新触发类加载。使用的就是上面注册的Transformer。
* retransformation可以修改方法体,但是不能变更方法签名、增加和删除方法/类的成员属性
*/
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
/**
* 获取一个对象的大小
*/
long getObjectSize(Object objectToSize);
/**
* 将一个jar加入到bootstrap classloader的 classpath里
*/
void appendToBootstrapClassLoaderSearch(JarFile jarfile);
/**
* 获取当前被JVM加载的所有类对象
*/
Class[] getAllLoadedClasses();
Передние два метода более важны, и после настройки метода ADDTRANSFORMER последующая загрузка класса будет перехвачена Transformer. Для загруженных классов можно выполнить RetransformClasses, чтобы повторно инициировать перехват этого Transformer. После того, как загруженный байт-код класса будет изменен, он не будет восстановлен, пока не будет получен снова.
Из вышеприведенного описания видно, что
- в состоянии пройти
Transformer
Изменить класс - Когда класс загрузится, его перехватит сработавший Transformer
2. Реализация
Нам нужно подсчитать время, затрачиваемое на метод, поэтому на ум приходит запись времени до выполнения метода и подсчет разницы во времени после выполнения, что является затратным по времени.
Прямая модификация байт-кода немного громоздка, поэтому мы используем артефактjavaassist
Модифицированный байткод
реализовать пользовательскийClassFileTransformer
, код показывает, как показано ниже
public class CostTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
// 这里我们限制下,只针对目标包下进行耗时统计
if (!className.startsWith("com/git/hui/java/")) {
return classfileBuffer;
}
CtClass cl = null;
try {
ClassPool classPool = ClassPool.getDefault();
cl = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
for (CtMethod method : cl.getDeclaredMethods()) {
// 所有方法,统计耗时;请注意,需要通过`addLocalVariable`来声明局部变量
method.addLocalVariable("start", CtClass.longType);
method.insertBefore("start = System.currentTimeMillis();");
String methodName = method.getLongName();
method.insertAfter("System.out.println(\"" + methodName + " cost: \" + (System" +
".currentTimeMillis() - start));");
}
byte[] transformed = cl.toBytecode();
return transformed;
} catch (Exception e) {
e.printStackTrace();
}
return classfileBuffer;
}
}
Затем немного измените агента
/**
* Created by @author yihui in 16:39 20/3/15.
*/
public class SimpleAgent {
/**
* jvm 参数形式启动,运行此方法
*
* manifest需要配置属性Premain-Class
*
* @param agentArgs
* @param inst
*/
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("premain");
customLogic(inst);
}
/**
* 动态 attach 方式启动,运行此方法
*
* manifest需要配置属性Agent-Class
*
* @param agentArgs
* @param inst
*/
public static void agentmain(String agentArgs, Instrumentation inst) {
System.out.println("agentmain");
customLogic(inst);
}
/**
* 统计方法耗时
*
* @param inst
*/
private static void customLogic(Instrumentation inst) {
inst.addTransformer(new CostTransformer(), true);
}
}
На этом агент завершен, и упаковка такая же, как в описанном выше процессе, а затем введите тестовую ссылку.
Создайте DemoClz с двумя методами.
public class DemoClz {
public int print(int i) {
System.out.println("i: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return i + 2;
}
public int count(int i) {
System.out.println("cnt: " + i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
return i + 1;
}
}
Тогда соответствующий основной метод выглядит следующим образом
public class BaseMain {
public static void main(String[] args) throws InterruptedException {
DemoClz demoClz = new DemoClz();
int cnt = 0;
for (int i = 0; i < 20; i++) {
if (++cnt % 2 == 0) {
i = demoClz.print(i);
} else {
i = demoClz.count(i);
}
}
}
}
Выбранный параметр агента JVM указывает запуск (то же, что и выше, и конкретную операцию), вывод выглядит следующим образом.
Несмотря на то, что в нашем приложении нет статистики затрат времени на методы, окончательный вывод отлично печатает затраты времени на вызов каждого метода, реализуя ненавязчивую функцию статистики затрат времени.
Пока что грамотность + реальный бой java агента в этой статье (статистика трудоемкости разработки метода) завершена.Значит можно сделать вывод?Нет, давайте представим проблему, возникшую в процессе реализация приведенной выше демонстрации.
3. Exception in thread "main" java.lang.VerifyError: Expecting a stack map frame
В примере с агентом, который демонстрирует трудоемкость метода, вместо использования исходного теста создается новый.DemoClz
сделать это, то почему он выбрал, что произойдет, если тестовые примеры в разделе 2 будут использоваться напрямую?
public class BaseMain {
public int print(int i) {
System.out.println("i: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return i + 2;
}
public void run() {
int i = 1;
while (true) {
i = print(i);
}
}
public static void main(String[] args) {
BaseMain main = new BaseMain();
main.run();
}
Еще укажите агента через параметр jvm, запустите приведенный выше код, вы обнаружите, что выбрасывается исключение и не может нормально работать.
Указано, что в методе запуска есть ошибка байт-кода.Считаем времязатратные Агенты, в основном, путем добавления новой строки кода перед запуском метода и после окончания метода.Мы напрямую добавляем его в метод run, который эквивалентен следующему коду
Приведенное выше подсказку четко говорит о том, что последняя строка утверждения никогда не может быть достигнута, и в компиляции будет исключение; тогда проблема в качестве поставщика агента Java, как я знаю, имеет ли у пользователя пути к Напишите такую бесконечную петлю, если в приложении есть такой бесконечный цикл задач, если я смогу моим агентом, приложение не сможет встать, кто этот горшок? ? ? ?
Ниже приведено решение, которое также очень простое: в параметре jvm добавьте-noverify
(Обратите внимание, что параметры могут быть разными для разных версий jdk, моя локальная — jdk8, используйте этот параметр, если это jdk7, можете попробовать-XX:-UseSplitVerifier
)
В среде разработки IDEA возможна следующая конфигурация
беги снова, нормально
4. Резюме
Данная статья является практическим проектом, в первую очередь уточняются параметры методаInstrumentation
Его определение интерфейса, через которое реализована модификация байт-кода java.
Мы реализуем на заказClassFileTransformer
, используйте javassist для изменения байт-кода, вставьте трудоемкую статистику в первую и последнюю строки каждого метода, чтобы получить трудоемкую статистику метода.
Наконец, есть небольшой вопрос. В приведенной выше реализации, когда внутри метода возникает исключение, будет ли последняя строка статистики, которую мы вводим, выводиться, как ожидалось? Если нет, как мы должны ее изменить? Добро пожаловать, чтобы оставить сообщение и указать решение
(Конкретное решение можно получить в исходном коде, а также есть вспомогательные тестовые примеры, поддержите, поставьте лайк, обратите внимание ❀)
II. Другое
0. Актуально
Связанный Боуэн
- 200303 - Как элегантно создавать статистические блоки кода в Java
- 200316-IDEA + проект Java-агента сборки maven с нулевой базой
Связанный исходный код
1. серый блог:liuyueyi.github.io/hexblog
Серый личный блог, записывающий все посты блога по учебе и работе, приглашаю всех в гости
2. Заявление
Это не так хорошо, как письмо веры.Контент уже был размещен, и это чисто из семьи.Из-за ограниченных личных возможностей неизбежно будут упущения и ошибки.Если вы найдете ошибки или лучше предложения, вы можете критиковать и исправлять их.
- Адрес вейбо:Блог Маленького Серого
- QQ: серо-серый / 3302797840
3. Сканируйте внимание
серый блог