Я работаю с несколькими сообществами в 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. Я не знаю, как мне удалось пропустить интеграцию модульных тестов. Это заставит меня использовать это намного больше в будущем.
И теперь я предлагаю просмотреть их документацию и начать вводить, есть что поиграть.