Home > наука > Знакомимся с JRebel

Знакомимся с JRebel

Осенью прошлого года на конференции JavaOne в Сан-Франциско, традиционно были присуждены награды лучшим проектам года “2011 Duke’s Choice Awards”И хотелось бы подготовить цикл небольших статей, коротко описывающих каждый из проектов-победителей.

Начнем данный цикл с номинации “Innovative Compiler for Java Code”, награду в которой получил проект JRebel, эстонской компании ZeroTurnaround, созданный Евгением Кабановым (Jevgeni Kabanov) и Томасом Румером (Toomas Römer).

Стоит отметить, что это не единственная их награда за прошедший год, этот проект был признан “Самой инновационной технологией Java” на JAX Innovation Awards. А в числе пользователей JRebel значатся IBM, HP, AT&T, Bank of America, Disney, LinkedIn, …

Для того, чтобы понять предназначение JRebel начнем чуть-чуть издалека

Рассмотрим два простых класса. Первый класс имеет лишь переопределенный метод toString():

public class TestModule {
    @Override
    public String toString() {
        return "TestModule, version 1!";
    }
}

Второй класс в бесконечном цикле создает экземпляр объекта первого класса и выводит его на экран:

public class Test {
  public static void main(String[] argv) {
    for (;;) {
            TestModule t = new TestModule();
            System.out.println(t);
            System.console().readLine();
    }
  }
}

Если же во время выполнения программы мы изменим строку “TestModule, version 1!” на “TestModule, version 2!”, и применяя раздельную компиляцию заново скомпилируем класс TestModule, то от этого значение выводимой строки не измениться.

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

Перечень загружаемых во время работы программы классов, и директории откуда они загружаются, можно узнать подставив при запуске программы ключик java -verbose:class …. Узнаете много чего интересного ;)

Из приведенного выше примера следует, то, что после изменения исходного кода (и, соответственно, байт-кода) необходимо перезапускать все приложение. А в случае промышленного (Java Web, Java EE) приложения заново разворачивать его на сервере. Вообщем-то логично.

Все бы ничего, но проведенные исследования показали, что разработчики тратят в среднем от 10 до 13 минут на час программирования, на периодическое развертывания приложений.

Чтобы сократить потери времени, необходимо “научить” JVM обновлять байт-код классов в случаи их изменений, без необходимости перезапуска приложения.

В простом случае для этого достаточно написать свой загрузчик классов, который наследуется от абстрактного класса ClassLoader, и переопределить в нем ряд методов таким образом, чтобы при каждом создании экземпляра класса его байт-код считывался с файловой системы. Это на самом деле делается не так страшно, как может показаться и подробно, вместе с примерами, описывается в статье “ClassLoader – скрытые возможности”.

Экземпляр собственного ClassLoader-а предается в качестве параметра статического метода Class.forName(String name, boolean initialize, ClassLoader loader), после чего с помощью рефлексии создается экземпляр требуемого класса:

public class Test {
  public static void main(String[] argv) throws Exception {
    for (;;) {
      ClassLoader loader = new DynamicClassOverloader(new String[] {"."});
      // текущий каталог "." будет единственным каталогом поиска для
      // класса "TestModule"
      Class clazz = Class.forName("TestModule",true,loader);
      Object object = clazz.newInstance();
      System.out.println(object);

      //TestModule test = (TestModule) object;

      System.console().readLine();
    }
  }
}

В данном примере DynamicClassOverloader (код по ссылке) как раз и является нашим загрузчиком классов (подробности в указанной статье “ClassLoader – скрытые возможности”).

Можно проверить, что теперь во время работы программы при изменении строки “TestModule, version 1!” на “TestModule, version 2!” будет меняться и выводимое в консоль сообщение.

Все было бы замечательно, если бы не ряд особенностей. Первая из которых начнется если откомментировать 10 строчку. В результате получим замечательное RuntimeException:

Exception in thread "main" java.lang.ClassCastException: TestModule cannot be cast to TestModule

Почему оно происходит? Запустим нашу программу с ключиком java -verbose:class Test, и обратим внимание на следующий фрагмент:

...
[Loaded TestModule from __JVM_DefineClass__]
TestModule, version 1!
[Loaded TestModule from file:/C:/Projects/DynamicClassLoader/build/classes/]
...

В первой строке происходит загрузка класса TestModule с помощью нашего собственного загрузчика классов, во второй – вывод сообщения, в третей – стандартный (системный) загрузчик классов снова загружает класс TestModule, именно в тот момент когда пытаемся осуществить приведение типов:

TestModule test = (TestModule) object;

Получается, что класс к которому мы хотим привести (преобразовать) объект загружен с помощью системного загрузчика, а класс на основании которого этот объект object был создан загружен с помощью нашего собственного загрузчика. Хоть классы одинаковы (и их байт-код идентичен), но загружены разными загрузчиками, и, как следствием, JVM считает их абсолютно разными.

Такую вот особенность, и далеко не единственную, которую нужно учитывать при написании собственного загрузчика классов.

Теперь, что же такое JRebel

JRebel – это плагин к JVM, который позволяет на лету перезагружать классы и другие ресурсы, которые были изменены с момента развёртывания приложения. При этом он не имеет тех проблем и особенностей, которые возникают при написании собственного загрузчика классов.

При загрузке класса в JVM, JRebel отслеживает соответствующий ему .class-файл и в случае его изменения подгружает изменившийся класс через расширенный загрузчик классов. При этом старые экземпляры классов и сам класс сохраняется, что позволяет приложению продолжать работу без потери данных. Новые же экземпляры класса будут создаются уже на основании обновленного байт-кода класса.

На данный момент JRebel поддерживает практически все изменения, которые могут осуществляться в исходном коде (JRebel Features):

  • Changes to method bodies
  • Adding/removing Methods
  • Adding/removing constructors
  • Adding/removing fields
  • Adding/removing classes
  • Adding/removing annotations
  • Changing static field value
  • Adding/removing enum values
  • Changing interfaces

за исключением изменений связанных с иерархией классов:

  • Replacing superclass
  • Adding/removing implemented interfaces

Есть ли аналоги? Да есть, но им далеко до возможностей JRebel. Сравнительную табличку аналогичных технологий и более подробное описание JRebel можно посмотреть в статье Get to Production Sooner.

Что еще посмотреть/почитать по теме

Также, компания  ZeroTurnaround недавно выпустила свой новый продукт LiveRebel. И если основной целью JRebel было сократить потери времени разработчиков на т.н. “Turnaround”, то LiveRebel в первую очередь рассчитан на обновление приложений на лету в “продакшане” без необходимости остановки их работы и с гарантией сохранения сессии.

Будем ждать таких же успехом LiveRebel!

P.S. Если кто-то захочет написать небольшую статью о других проектах получивших награды  “2011 Duke’s Choice Awards”, не стесняйтесь ;)

За помощь в подготовке данного материа спасибо Полянскому Дмитрию

  1. Artem
    January 17th, 2012 at 11:02 | #1

    Интересный пример с приведением объекта к типу того же класса из другого загрузчика. Буду иметь ввиду :)

  2. January 17th, 2012 at 23:19 | #2

    Еще интересные примеры и ошибки связанные с загрузчиком классов приводит в своем выступлении на Jazoon Jevgeni Kabanov “Do you really get class loaders?” (http://zeroturnaround.com/blog/reloading-objects-classes-classloaders/)

  3. February 5th, 2012 at 14:26 | #3

    хороший обзор, спасибо :)

  4. February 8th, 2012 at 00:13 | #4

    Спасибо, Антон, за отзыв!
    Как-нибудь и про LiveRebel напишем (очень доклад Евгения на Devoxx понравился ;))

    @Anton Arhipov

  5. March 11th, 2012 at 14:27 | #5

    Запись выступления Антона Архипова с рассказом о принципах работы JRebel http://anton-arhipov.livejournal.com/219099.html

  1. March 11th, 2012 at 15:13 | #1
  2. November 4th, 2012 at 23:48 | #2

*