У платформи java є дві особливості. Для забезпечення платформ програма спочатку компілюється в проміжна мова низького рівня - байт-код. Друга особливість завантаження виконуваних класів відбувається за допомогою розгортаються classloader. Це механізм забезпечує більшу гнучкість і дозволяє модифікувати виконуваний код при завантаження, створювати і довантажувати нові класи під час виконання програми.
Така техніка широко застосовується для реалізації AOP, створення тестових фреймворк, ORM. Особливо хочеться відзначити terracotta, продукт з гарною ідеєю кластеризації jvm і на всю котушку використовує модифікації байт-коду. Ця замітка буде присвячена огляду структури байт-коду, першій частині цієї сильної зв'язки.
Кожному класу в java відповідає один скомпільованій файл. Це справедливо навіть для підкласів або анонімним класів. Такий файл містить інформацію про ім'я класу, його батьки, список інтерфейсів які він реалізує, перерахування його полів і методів. Важливо відзначити, що після компіляції інформації що містить директива import втрачається і всі класи іменуються тепер через повний шлях. Наприклад в місце String буде записано java / lang / String.
Найцікавіше як будуть виглядати методи класу в байт-коді. Будемо спостерігати во, що трансформується наступний клас:
package org;
class Test (
private String name;
public String getName () (
return name;
)
public void setName (String name) (
this.name = name;
)
)
Почнемо з заголовка. У ньому міститься інформація про назву методу те, що метод викликається без параметрів і тип повертається аргументу.
Байт-код стекол-орієнтована мова, схожий за своєю структурою на асемблер. Що б провести операції з даними їх спочатку потрібно покладе на стек. Ми хочемо взяти полі у об'єкт. Що б це сделять потрібно його покласти в стек. У байт-коді немає імен змінних, у них є номери. Нульовий номер у посилання на поточний об'єкт або у зміною this. Потім йдуть параметри для виконання методу. Потім інші змінні.
Команда ALOAD 0 кладе змінну this на стек. Що б на стек покласти тип даних, відмінний від посилання потрібно скористатися іншою командою. Для long буде LLOAD, а для doubles [] буде DALOAD.
Наступна команда GETFIELD, прибирає зі стека посилання на об'єкт і кладе примітивний тип або посилання на поле даного об'єкта. У неї є два параметри. Перший ім'я класу, друге ім'я змінної. Якщо ж змінна статична, то попередньо класти на стек нічого не потрібно, а команду потрібно замінити на GETSTATIC з темі ж параметрами.
Остання команда говорить, що метод завершений і повертає значення типу посилання зі стека.
Сетер має трохи більш складну структуру.
public setName (Ljava / lang / String;) V
ALOAD 0
ALOAD 1
PUTFIELD org / Test name
RETURN
Даний метод нічого не повертає. Перші дві команди кладуть на стек змінну this і параметр для виконання методу. Потім викликається команда PUTFIELD (PUTSTATIC для статичного поля) яка встановить значення поля об'єкту і прибере зі стека останні два значення. Остання команда вихід з методу.
Додамо до нашого об'єкт ще пару методів і подивимося який байт-код їм відповідає.
public void forTest (Boolean b) (
System.out.prinln (b);
)
public Long testMethods (Collection
Long a = System.curretM ();
forTest (testInterface.contains (a));
return a;
)
testMethod має такі уявлення.
INVOKESTATIC java / lang / System currentTimeMillis () J
LSTORE 2
ALOAD 0
ALOAD 1
LLOAD 2
INVOKESTATIC java / lang / Long valueOf (J) Ljava / lang / Long;
INVOKEINTERFACE java / util / Collection contains (Ljava / lang / Object;) Z
INVOKESTATIC java / lang / Boolean valueOf (Z) Ljava / lang / Boolean;
INVOKEVIRTUAL org / Test forTest (Ljava / lang / Boolean;) V
LLOAD 2
INVOKESTATIC java / lang / Long valueOf (J) Ljava / lang / Long;
ARETURN
Перша команда викликає статичний метод у класу System. Друга запам'ятовує результат виклику методу currentTimeMillis у змінній з другим номером. Потім ми кладемо змінну this, параметр методу і змінну з номером 2 на стек. Перетворять змінну до типу java / lang / Long. І перевіряємо, що вона в нас міститься в колекції, викликаючи метод у параметра виконується. У нас параметр інтерфейс, тому застосовується команда INVOKEINTERFACE. Для методу класу необхідно використовувати INVOKEVIRTUAL. Що б викликати метод у об'єкт або інтерфейсу необхідно, що б на стеку лежав об'єкт, потім параметри методу що викликається. У результаті виклику методу вони замінюватися на результат або просто заберуться з стека, якщо метод нічого повертає. Остання три команди кладуть зміну на стек, перетворюють її на об'єкт і повертають її як значення методу.
Що б завершити наш екскурс в байт-код додамо останній метод і подивимося на цикли і умовні оператори.
public void testAriphmentics () (
int i = -17;
while (i <10) (
if (i <0) (
i = i + 7;
)
i = i * 13;
)
)
Він в байт-коді буде виглядати так
ACC_FINAL -17
ISTORE 1
Label: L1466604866
ILOAD 1
ACC_FINAL 10
IF_ICMPGE L329949514
ILOAD 1
IFGE L658705244
ILOAD 1
ACC_FINAL 7
IADD
ISTORE 1
Label: L658705244
ILOAD 1
ACC_FINAL 13
IMUL
ISTORE 1
GOTO L1466604866
Label: L329949514
RETURN
Перші дві команди ініціалізує змінну i (з номером 1) значенням -17.
Далі у нас починається тіло циклу. Для реалізації якого буде потрібно мітки,
команда переходу і умовний оператор. У тілі нашого методу аж цілих три позначок. Перша позначка означає початок циклу. Друга потрібна для умовного оператора, а остання знаменує кінець циклу. Умовного оператор має один параметр мітку переходу. Перед тим, як його викликати сравніваеми значення повинні лежати на стеку. Для порівняння з нулем використовується окрема команда. Для кожного типу є свій оператор порівняння. Для int він IF_ICMPGE. Після порівняння сравніваеми значення забираються зі стека. Для арифметичних дій з двома змінним їх так само як і для умовного оператора потрібно попередньо покласти на стек. Після виконання вони знімаються з стека, а на їх місце кладеться результат.
На це короткий екскурс в байт-код закінчено, деякі питання такі як виключення, синхронізація не були порушені. Я сподіваюся, що маючи уявлення про байт коді читач легко впоратися з ними. У наступній частині ми розглянемо інструменти які застосовуються для модифікації байт-коду.
http://math-and-prog.blogspot.com



Немає коментарів:
Дописати коментар