Я работаю с несколькими сообществами в JBoss, и есть так много интересных вещей, о которых можно поговорить, так что я не могу каждый раз оборачиваться. Это главная причина, почему я очень благодарен за возможность время от времени приглашать сюда блоггеров-гостей. Сегодня это Йохен Мадер, который является частью стада ботаников в Codecentric. В настоящее время он тратит свое профессиональное время на программирование промежуточного программного обеспечения на базе Vert.x, написание статей для различных публикаций и выступление на конференциях. Его свободное время принадлежит его семье, mtb и настольным играм. Вы можете подписаться на него в Твиттере @codepitbull .
Есть инструменты, которые вы обычно не хотите использовать, но достаточно рады узнать о них, когда возникнет такая необходимость. По крайней мере, для меня Байтман относится к этой категории. Это мой личный швейцарский армейский нож, чтобы иметь дело с « Большим шариком грязи» или одним из тех страшных гейзенгов. Так что возьмите текущий дистрибутив Byteman , разархивируйте его где-нибудь на вашей машине, и мы приступим к грязной работе.
Что это такое
Byteman — это набор инструментов для манипуляции с байтовым кодом и его инъекции. Это позволяет нам перехватывать и заменять произвольные части кода Java, чтобы заставить его вести себя иначе или нарушать его (нарочно):
- получить все темы застрял в определенном месте, и пусть они продолжаются в то же время (привет гонка условие)
- выбросить исключения в неожиданных местах
- отслеживание вашего кода во время выполнения
- изменить возвращаемые значения
и многое другое.
Пример
Давайте разберемся в некотором коде, чтобы проиллюстрировать, что Байтман может сделать для вас.
Здесь у нас есть замечательный синглтон и (к сожалению) хороший пример кода, который вы можете найти во многих местах.
public class BrokenSingleton {
private static volatile BrokenSingleton instance;
private BrokenSingleton() {
}
public static BrokenSingleton get() {
if (instance == null) {
instance = new BrokenSingleton();
}
return instance;
}
}
Давайте представим, что мы — бедняги, которым поручено отладить некоторый устаревший код, показывающий странное поведение на производстве. Через некоторое время мы обнаруживаем этот драгоценный камень, и наша интуиция указывает на то, что здесь что-то не так.
Сначала мы можем попробовать что-то вроде этого:
public class BrokenSingletonMain {
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(new SingletonAccessRunnable());
Thread thread2 = new Thread(new SingletonAccessRunnable());
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
public static class SingletonAccessRunnable implements Runnable {
@Override
public void run() {
System.out.println(BrokenSingleton.get());
}
}
}
Выполнение этого дает очень маленький шанс увидеть реальную проблему. Но, скорее всего, мы не увидим ничего необычного. Синглтон инициализируется один раз, и приложение работает как положено. Часто люди начинают грубо форсировать, увеличивая количество потоков, надеясь, что проблема проявится. Но я предпочитаю более структурированный подход.
Введите Байтман.
DSL
Byteman предоставляет удобный DSL для изменения и отслеживания поведения приложения. Мы начнем с отслеживания звонков в моем маленьком примере. Взгляните на этот кусок кода.
RULE trace entering
CLASS de.codepitbull.byteman.BrokenSingleton
METHOD get
AT ENTRY
IF true
DO traceln("entered get-Method")
ENDRULE
RULE trace read stacks
CLASS de.codepitbull.byteman.BrokenSingleton
METHOD get
AFTER READ BrokenSingleton.instance
IF true
DO traceln("READ:\n" + formatStack())
ENDRULE
Основным строительным блоком Byteman-скриптов является ПРАВИЛО.
Он состоит из нескольких компонентов (пример беззастенчиво оторванного от Byteman-Docs :
# rule skeleton RULE <rule name> CLASS <class name> METHOD <method name> BIND <bindings> IF <condition> DO <actions> ENDRULE
Каждое ПРАВИЛО должно иметь уникальное имя __rule__. Комбинация CLASS и METHOD определяет, где мы хотим применить наши модификации. BIND позволяет нам связывать переменные с именами, которые мы можем использовать внутри IF и DO. Используя IF, мы можем добавить условия, при которых правило срабатывает. В DO происходит настоящая магия.
ENDRULE, это заканчивает правило.
Понимая это, мое первое правило легко переводится на:
Когда кто-то вызывает _de.codepitbull.byteman.BrokenSingleton.get () _ Я хочу напечатать строку «enter get-Method» прямо перед вызовом тела метода (это то, что __AT ENTRY__ переводит к).
Мое второе правило можно перевести на:
После прочтения (__AFTER READ__) экземпляра-члена BrokenSingleton я хочу увидеть текущий стек вызовов.
Возьмите код и поместите его в файл с именем _check.btm_. Byteman предоставляет хороший инструмент для проверки ваших скриптов. Используйте __ <bytemanhome> /bin/bmcheck.sh -cp folder / Содержит / Compiled / classes / to / test check.btm__, чтобы увидеть, компилируется ли ваш скрипт. Делайте это КАЖДЫЙ раз, когда вы меняете его, очень легко ошибиться в деталях и потратить много времени на его выяснение.
Теперь, когда скрипт сохранен и протестирован, пришло время использовать его с нашим приложением. Сценарии
агента
применяются для выполнения кода через агента. Откройте конфигурацию запуска для __BrokenSingletonMain-class__ и добавьте
__-javaagent:<BYTEMAN_HOME>/lib/byteman.jar=script:check.btm__
к вашим JVM-параметрам. Это зарегистрирует агента и скажет ему запустить _check.btm_.
И пока мы на него здесь несколько вариантов больше:
Если вы когда — нибудь понадобится , чтобы манипулировать некоторые ядра использования Java вещи
__-javaagent:<BYTEMAN_HOME>/lib/byteman.jar=script:appmain.btm,boot:<BYTEMAN_HOME>/lib/byteman.jar__
Это добавит Byteman в загрузочный путь к классам и позволит нам манипулировать такими классами, как _Thread_, _String_ … Я имею в виду, если вы когда-нибудь хотели такие неприятные вещи …
Также возможно подключить агент к запущенному процессу. Нас __jps__, чтобы найти идентификатор процесса, к которому вы хотите присоединиться и запустить
__<bytemanhome>/bin/bminstall.sh <pid>__
установить агент. После бега
__<bytemanhome>/bin/bmsubmit.sh check.btm__
Вернемся к нашей проблеме под рукой.
Запуск нашего приложения с измененной конфигурацией запуска должен привести к выводу, подобному этому
entered get-Method entered get-Method READ: Stack trace for thread Thread-0 de.codepitbull.byteman.BrokenSingleton.get(BrokenSingleton.java:14) de.codepitbull.byteman.BrokenSingletonMain$SingletonAccessRunnable.run(BrokenSingletonMain.java:20) java.lang.Thread.run(Thread.java:745) READ: Stack trace for thread Thread-1 de.codepitbull.byteman.BrokenSingleton.get(BrokenSingleton.java:14) de.codepitbull.byteman.BrokenSingletonMain$SingletonAccessRunnable.run(BrokenSingletonMain.java:20) java.lang.Thread.run(Thread.java:745)
Поздравляю, вы только что манипулировали байтовым кодом. Результат пока не очень полезен, но это то, что мы собираемся изменить.
Возиться с потоками.
Теперь, когда наша инфраструктура настроена, мы можем начать копать глубже. Мы совершенно уверены, что наша проблема связана с проблемой многопоточности. Чтобы проверить нашу гипотезу, мы должны одновременно добавить несколько потоков в наш критический раздел. Это практически невозможно при использовании чистой Java, по крайней мере, без внесения значительных изменений в код, который мы хотим отладить.
С помощью Byteman это легко достигается.
RULE define rendezvous
CLASS de.codepitbull.byteman.BrokenSingleton
METHOD get
AT ENTRY
IF NOT isRendezvous("rendezvous", 2)
DO createRendezvous("rendezvous", 2, true);
traceln("rendezvous created");
ENDRULE
Это правило определяет так называемое рандеву. Это позволяет нам указать место, куда должны приходить несколько потоков, пока им не разрешат продолжить (также известный как барьер).
И вот перевод для правила:
при вызове _BrokenSingleton.get () _ создайте новое рандеву, которое позволит прогрессировать, когда прибывают 2 потока. Сделайте рандеву многоразовым и создайте его только в том случае, если он не существует (если IF NOT не важен, иначе мы бы создали барьер при каждом вызове _BrokenSingleton.get () _).
После определения этого барьера нам все еще нужно явно его использовать.
RULE catch threads
CLASS de.codepitbull.byteman.BrokenSingleton
METHOD get
AFTER READ BrokenSingleton.instance
IF isRendezvous("rendezvous", 2)
DO rendezvous("rendezvous");
ENDRULE
Перевод: После прочтения _instance_-member внутри _BrokenSingleton.get () _ дождитесь встречи, пока не прибудет второй поток, и продолжите вместе.
Теперь мы останавливаем оба потока из _BrokenSingletonMain_ в одном и том же шнурке после проверки экземпляра на ноль. Вот как сделать условия гонки воспроизводимыми. Оба потока будут продолжать думать, что _instance_ равен нулю, в результате чего конструктор запускается дважды.
Я оставляю вам решение этой проблемы …
Юнит-тесты
Что-то, что я обнаружил во время написания этого поста в блоге, это возможность запускать Byteman-скрипты как часть моих юнит-тестов. Их JUNit- и TestNG-интеграция легко интегрируются.
Добавьте следующую зависимость в ваш _pom.xml_
<dependency>
<groupId>org.jboss.byteman</groupId>
<artifactId>byteman-submit</artifactId>
<scope>test</scope>
<version>${byteman.version}</version>
</dependency>
Теперь Byteman-скрипты могут выполняться внутри ваших юнит-тестов следующим образом:
@RunWith(BMUnitRunner.class)
public class BrokenSingletonTest
{
@Test
@BMScript("check.btm")
public void testForRaceCondition() {
...
}
}
Добавление таких тестов в ваши костюмы значительно увеличивает полезность Byteman. Нет лучшего способа помешать другим повторить ваши ошибки, если сделать эти сценарии частью процесса сборки.
Заключительные слова
В блоге очень мало места, и я тоже не хочу переписывать их документацию. Написание этого поста было забавным занятием, так как я давно не использовал Byteman. Я не знаю, как мне удалось пропустить интеграцию модульных тестов. Это заставит меня использовать это намного больше в будущем.
И теперь я предлагаю просмотреть их документацию и начать вводить, есть что поиграть.