Java agent на службі jvm


Напевно багато хто чув або стикалися з таким параметром JVM як -javaagent, побачити цей параметр ви могли використовуючи Jrebel або Plumbr це могло виглядати наприклад так JAVA_OPTS = -javaagent: [path / to /] jrebel.jar або так -javaagent: / path-to /plumbr.jar
Хоча javaagent з'явився ще в версії java 1.5, багато розробників так ніколи і не використали можливості агентів і мають туманне уявлення що це таке.
Що ж це за агент? Навіщо він може нам знадобитися і як написати свій?

Як я написав вище javaagent це один з параметрів JVM, який дозволяє вказати агент який буде запущений з вашим додатком, а точніше він буде запущений ще перед запуском вашого застосування. Сам агент це окремий додаток яке надає доступ до механізму маніпуляції байт-кодом (java.lang.instrument) в runtime. Це якщо коротко. Офіційну документацію можна почитати тут. але вона досить убога. Нічого не зрозуміло? Отже, давайте розбиратися. Найкраще розбиратися на прикладах.

Напишемо елементарний агент

Зверніть увагу, агент обов'язково повинен реалізовувати метод premain з наступною сигнатурою
public static void premain (String args);
або
public static void premain (String args, Instrumentation inst);

Клас агента повинен бути упакований в jar і містити MANIFEST.MF. з обов'язковим атрибутом
PreMain-Class - вказує на клас агента з premain методом. Є й інші атрибути агента, але вони необов'язкові і зараз нам не знадобляться.

Ось так буде виглядати наш manifest.mf.
не забудьте додати новий рядок в кінець файлу

Тепер спакуємо все це в jar

І нарешті клас випробувач

Запускаємо AgentTester з командного рядка

З цього прикладу видно що:
  • метод premain виповнюється ще до виклику методу main основного додатка.
  • агент вказується за допомогою параметра -javaagent: jarpath [= options]

Давайте спробуємо витягти з агента якусь користь


Взагалі механізм агентів призначений для маніпуляції байт-кодом. але скажу відразу модифікувати байт-код в цій статті ми не будемо інакше можна піти далеко-далеко за межі цієї посади. Кому цікаво можна подивитися на javassist так як стандартних засобів для роботи з байт-кодом немає.

Напишемо AgentCounter який буде виводити ім'я завантаженого клас і підраховувати кількість завантажених класів. Так ми зможемо поспостерігати за роботою classloader`a.


Зверніть увагу, тепер я використовую іншу сигнатуру методу premain. В об'єкт instrumentation я передаю ClassTransformer який і виконує всю роботу. ClassTransformer буде спрацьовувати кожного разу при завантаженні класу. Якщо ви хочете використовувати свій ClassTransformer, ви повинні реалізувати інтерфейс java.lang.instrument.ClassFileTransformer і додати свій об'єкт через метод Instrumentation.addTransformer


classfileBuffer - це і є байт-код поточного класу представлений у вигляді масиву байт, для його перевизначення трансформер повинен повернути новий масив байт, в даному прикладі ми не змінюємо вміст класу тому просто повертаємо той же масив.

Пакуємо агент і трансформер в новий jar

Трохи модифікуємо клас тестер

Запускаємо AgentTester c новим агентом
для різних версій java результати можуть відрізнятися

Якщо запустити якусь enterprise додаток з таким агентом, можна отримати досить цікаві результати, наприклад один з проектів після старту видав мені наступне:

Вимірюємо розмір java об'єктів


Розглянемо ще один приклад використання агентів. Напишемо клас який буде повертати розмір java об'єктів і javaagent буде грати ключову роль. Хто як не JVM може знати реальний розмір створеного об'єкта. в інтерфейсі Instrumentation є чудовий метод long getObjectSize (Object objectToSize) який повертає розмір об'єкта. Але як з нашого застосування отримати доступ до агенту? А робити нічого особливого й не доведеться, javaagent автоматично додається в classpath і нам залишається тільки додати в агент поле типу Instrumentation instrumentation та форматувати його в методі premain.


Ми отримуємо доступ до методу AgentMemoryCounter.getSize (obj) з класу додатка.

Результати роботи програми можу виглядати наступним чином

Зверніть увагу що метод getObjectSize () не враховує розмір вкладених об'єктів тобто враховується тільки пам'ять витрачена на посилання на об'єкт.

висновок

> AgentCounter який буде виводити ім'я завантаженого клас і підраховувати кількість завантажених класів
А чи є спосіб підраховувати кількість примірників даного класу

Завжди дуже цікавило як з'ясувати: «Хто стільки створив стільки рядків або int [], а знайти батька цих блоків?»
Можливо це вирішено вже за допомогою якогось вже існуючого агента або відладчика?

Для цього є профайлери. Наприклад YourKit Java Profiler. З безкоштовного, наприклад Eclipse Memory Analyzer Tool.

Час вказано в тому часовому поясі, який встановлений на вашому телефоні.