2010-07-19 2 views
7

Я пишу прибор для байт-кода. Прямо сейчас, я пытаюсь выяснить, как это сделать при наличии объектов. Я хотел бы некоторые разъяснения по двум линиям, которые я прочитал в JVMs (раздел 4.9.4):Уточнения по байт-коду и объектам

1) «Испытатель отвергает код, который использует новый объект, прежде чем он был инициализирован.»

Мой вопрос: что здесь означает «использует»? Я предполагаю, что это означает: передавать его как атрибут метода, вызывая на нем GETFIELD и PUTFIELD или вызывающий на нем какой-либо метод экземпляра. Являются ли их другие запретные виды использования? И я считаю, что следует, что допускаются другие инструкции, такие как DUP, LOAD и STORE.

2) «Перед тем, что метод вызывает другой метод инициализации экземпляра MyCLASS или его прямой суперкласс на этого, единственную операцию метод может выполнить на этом назначении поля, объявленное в пределах MyCLASS.»

Это означает, что в <init> методы ПолучитьПолеЗаголовок и PUTFIELD разрешены перед другим <init> называется. Однако в Java, выполняющем любую операцию в поле экземпляра перед вызовом super() или this(), возникает ошибка компиляции. Может ли кто-нибудь прояснить это?

3) У меня есть еще один вопрос. Когда инициализируется ссылка на объект и, следовательно, готова к свободному использованию? От чтения JVMS я придумал ответ, что независимо от того, инициализирован ли объект или нет, зависит от каждого метода. В определенный момент времени объект может быть инициализирован для метода, но не для другого. В частности, объект инициализируется для метода, когда возвращается <init>, вызываемый этим методом.

Например, обратите внимание, что метод main() создал объект и вызвал <init>, который затем назывался суперклассом <init>. После возврата из super() объект теперь считается инициализированным <init>, но еще не инициализирован для main(). Означает ли это, что в <init> после super() я могу передать объект в качестве параметра методу, даже до возврата из main().

Может ли кто-нибудь подтвердить, что весь этот анализ верен? Спасибо за ваше время.

ps: Я фактически опубликовал тот же вопрос на форумах Sun, но с ответом. Надеюсь, мне повезет больше. Спасибо.

Update

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

Главное, что я обнаружил, это то, что поведение Verifier отличается различными реализациями и версиями (что значительно затрудняет работу манипуляции с байт-кодом).Проблема заключается либо в несоответствии JVMS, либо в отсутствии документации от разработчиков верификатора, либо у JVMS есть некоторые тонкие неопределенности в области верификатора.

Последнее, SO Rocks !!! Я опубликовал тот же вопрос на официальном форуме Sun JVM Specifications, и до сих пор я до сих пор не получил ответа.

ответ

1

Предлагаю вам загрузить копию источников OpenJDK и посмотреть, что проверяет верификатор. Если ничего больше, это может помочь вам понять, что говорит спецификация JMV.

(Тем не менее, @Joachim прав. Опираясь на то, что реализация проверяющий делает, а не то, что спецификация говорит довольно рискованно.)

+0

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

+0

Да, я обнаружил, что разные реализации Verifier имеют разный уровень строгости. Например, верификатор JustIce Apache BCEL не разрешает STORE на неинициализированных ссылках на объекты, в то время как Java HotSpot 10.0 делает –

4

«Испытатель отвергает код, который использует новый объект перед ним был инициализирован ».

В проверке байткода, так как верификатор работает во время привязки, выводятся типы локальных переменных методов. Типы аргументов метода известны как они есть в сигнатуре метода в файле класса. Типы других локальных переменных неизвестны и выводятся, поэтому я предполагаю, что «использует» в приведенном выше описании относится к этому.

EDIT: Раздел 4.9.4 из JVMS гласит:

Способ инициализации экземпляра (§3.9) для класса MyClass видит новый неинициализированный объект, как это его аргумент в локальной переменной 0. До этого метода вызывает другой метод инициализации экземпляра myClass или его прямого суперкласса на этом, единственная операция, которую может выполнять этот метод, - это присвоение полей, объявленных в myClass.

Это присвоение полей в приведенном выше описании является «начальной» инициализацией переменных экземпляра исходными значениями по умолчанию (например, int is 0, float равно 0.0f и т. Д.), Когда выделена память для объекта. Существует еще одна «правильная» инициализация переменных экземпляра, когда виртуальная машина вызывает метод инициализации экземпляра (конструктор) объекта.


link, предоставленный Джоном Хорстманом, помог прояснить ситуацию. Таким образом, эти утверждения не сохраняются. «Этот DOESNOT означает, что в методе <init>getfield и putfield разрешены до того, как вызывается еще <init>». Инструкции getfield и putfield используются для доступа (и изменения) переменных экземпляра (полей) класса (или экземпляра класса). И это может произойти только тогда, когда переменные экземпляра (поля) инициализируются «

Из JVMs:.

Каждый экземпляр метода инициализации (§3.9), за исключением способа инициализации экземпляра , полученный из конструктор класса Object, должен вызова либо другой экземпляр метод инициализации этого или метод инициализации экземпляра его прямых суперкласс супер перед своими членами экземпляра доступен. Однако поля экземпляра этого типа, которые объявлены в текущем классе, могут назначаться перед вызовом любого метода инициализации экземпляра .

Когда виртуальная машина Java создает новый экземпляр класса, явно или неявно, она первая выделяет память в куче для хранения переменных экземпляра объекта. Память выделяется для всех переменных, объявленных в классе объекта и во всех его суперклассах, включая скрытые переменные экземпляра. Как только виртуальная машина отложит кучу памяти для нового объекта, она немедленно инициализирует переменные экземпляра исходными значениями по умолчанию. После того как виртуальная машина выделила память для нового объекта и инициализировала переменные экземпляра значениям по умолчанию, она готова предоставить переменным экземпляра свои правильные начальные значения. Виртуальная машина Java использует два метода для этого, в зависимости от того, создается ли объект из-за вызова clone(). Если объект создается из-за клонирования(), виртуальная машина копирует значения переменных экземпляра объекта, клонированного в новый объект. В противном случае виртуальная машина вызывает метод инициализации экземпляра объекта. Метод инициализации экземпляра инициализирует переменные экземпляра объекта соответствующими начальными значениями. И только после этого вы можете использовать getfield и putfield.

Компилятор java генерирует по крайней мере один метод инициализации экземпляра (конструктор) для каждого компилируемого класса. Если класс явно не объявляет конструкторы, компилятор сгенерировал конструктор no-arg по умолчанию, который просто вызывает конструктор no-arg суперкласса. Правильно, поэтому любая операция в поле экземпляра перед вызовом super() или this() приводит к ошибке компиляции.

<init> Способ может содержать три вида кода: вызов другого метода, <init> кода, который реализует любые инициализаторы переменных экземпляра, и код для тела конструктора. Если конструктор начинается с явного вызова другого конструктора в том же классе (а this() вызова) его соответствующий <init> метод будет состоять из двух частей:

  • призывание одного и того же класса <init> метод
  • байткоды, реализующие тело соответствующего конструктора

Если конструктор не начинается с this() вызова и класс не объект, <init> метод будет иметь три компонента:

  • призывание суперкласса <init> метода
  • байткодов для любого экземпляра инициализаторов переменных
  • байткодов, которые реализуют тело соответствующего конструктор


Если конструктор не начинается с вызова this() а класс - Object (и Object не имеет суперкласса), то его метод <init> не может начинаться с вызова метода суперкласса <init>. Если конструктор начинается с явного вызова конструктора суперкласса (вызов super()), его метод <init> будет вызывать соответствующий метод суперкласса <init>.



Я думаю, что это ответ на ваш первый и второй вопрос.

Обновлено:

Например,

class Demo 
    { 
    int somint; 

    Demo() //first constructor 
    { 
     this(5); 
     //some other stuff.. 
    } 

    Demo(int i) //second constructor 
    { 
     this.somint = i; 
     //some other stuff...... 
    } 
    Demo(int i, int j) //third constructor 
    { 
     super(); 
     //other stuffff...... 
    } 
    } 

Heres байт-код для указанных выше трех конструкторов из компилятором (Javac):

Demo(); 
    Code: 
    Stack=2, Locals=1, Args_size=1 
    0: aload_0 
    1: iconst_5 
    2: invokespecial #1; //Method "<init>":(I)V 
    5: return 

Demo(int); 
    Code: 
    Stack=2, Locals=2, Args_size=2 
    0: aload_0 
    1: invokespecial #2; //Method java/lang/Object."<init>":()V 
    4: aload_0 
    5: iload_1 
    6: putfield  #3; //Field somint:I 
    9: return 

Demo(int, int); 
    Code: 
    Stack=1, Locals=3, Args_size=3 
    0: aload_0 
    1: invokespecial #2; //Method java/lang/Object."<init>":()V 
    4: return 

В первом конструктора, метод <init> начинается с вызова метода <init> того же класса а затем выполнил тело соответствующего конструктора. Поскольку конструктор начинается с this(), его соответствующий метод <init> не содержит байт-код для инициализации переменных экземпляра.

Во втором конструкторе, метод <init> для конструкторы имеет

  • супер класс <init> метод, т.е. вызова суперкласса конструктора (нет метода Arg), то компилятора это по умолчанию потому что в явном виде super() не найден .
  • байт-код для инициализации переменной экземпляра someint.
  • байт-код для остальной части корпуса корпус конструктора.
+0

PS: Я сам не написал инструмент для байт-кода, но в последнее время я читал материал на JVM, как внутри JVM. – Zaki

+0

Первое спасибо за ваше время !!! У меня еще мало вопросов: вы сказали, что конструктор без этого() имеет 3 компонента. Тем не менее, я прочитал байт-код, и компилятор никогда не добавляет байт-коды для инициализаторов переменных экземпляра (0 для int, null для объектов). Кажется, что JVM инициализирует поля экземпляра без необходимости использования байт-кода. –

+0

@HH, см. Обновленный ответ – Zaki

3

Вопреки тому, что язык Java определяет, на уровне байт-кода это является можно получить доступ к полям класса в конструкторе до вызова конструктора суперкласса. Следующий код использует библиотеку ассемблера для создания такого класса:

package asmconstructortest; 

import java.io.FileOutputStream; 
import org.objectweb.asm.*; 
import org.objectweb.asm.util.CheckClassAdapter; 
import static org.objectweb.asm.Opcodes.*; 

public class Main { 

    public static void main(String[] args) throws Exception { 
     //ASMifierClassVisitor.main(new String[]{"/Temp/Source/asmconstructortest/build/classes/asmconstructortest/Test.class"}); 
     ClassWriter cw = new ClassWriter(0); 
     CheckClassAdapter ca = new CheckClassAdapter(cw); 

     ca.visit(V1_5, ACC_PUBLIC + ACC_SUPER, "asmconstructortest/Test2", null, "java/lang/Object", null); 

     { 
      FieldVisitor fv = ca.visitField(ACC_PUBLIC, "property", "I", null, null); 
      fv.visitEnd(); 
     } 

     { 
      MethodVisitor mv = ca.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 
      mv.visitCode(); 
      mv.visitVarInsn(ALOAD, 0); 
      mv.visitInsn(ICONST_1); 
      mv.visitFieldInsn(PUTFIELD, "asmconstructortest/Test2", "property", "I"); 
      mv.visitVarInsn(ALOAD, 0); 
      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); 
      mv.visitInsn(RETURN); 
      mv.visitMaxs(2, 1); 
      mv.visitEnd(); 
     } 

     ca.visitEnd(); 

     FileOutputStream out = new FileOutputStream("/Temp/Source/asmconstructortest/build/classes/asmconstructortest/Test2.class"); 
     out.write(cw.toByteArray()); 
     out.close(); 
    } 
} 

Instantiation этот класс работает нормально, без каких-либо ошибок проверки:

package asmconstructortest; 

public class Main2 { 
    public static void main(String[] args) { 
     Test2 test2 = new Test2(); 
     System.out.println(test2.property); 
    } 
} 
+1

Я также тестировал эту идею, используя jbe (редактор байт-кода java), и он проходит проверку на HotSpot 10 (но не на JustIce Apache BCEL) –

Смежные вопросы