Разъяснения по байт-коду и объектам - PullRequest
7 голосов
/ 19 июля 2010

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

1) "Верификатор отклоняет код, который использует новый объект, прежде чем он был инициализирована. "

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

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

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

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

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

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

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

Обновление

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

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

И последнее, ТАК, Скалы !!! Я опубликовал тот же вопрос на официальном форуме спецификаций Sun JVM, и до сих пор не получил ответа.

Ответы [ 3 ]

4 голосов
/ 19 июля 2010

"Верификатор отклоняет код, который использует новый объект до его инициализации."

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

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

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

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


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

Из JVMS:

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

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

ThКомпилятор Java генерирует по крайней мере один метод инициализации экземпляра (конструктор) для каждого класса, который он компилирует. Если класс явно не объявляет конструкторов, компилятор генерирует конструктор по умолчанию без аргументов, который просто вызывает конструктор суперкласса без аргументов. И правильно, если любая операция над полем экземпляра перед вызовом 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......
     }
  }

Вот байт-код для трех вышеупомянутых конструкторов из компилятора (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.
  • байт-код для остальных вещей в тело конструктора.
3 голосов
/ 19 июля 2010

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

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();
    }
}

Инстанциация этого класса работает нормально, без ошибок проверки:

package asmconstructortest;

public class Main2 {
    public static void main(String[] args) {
        Test2 test2 = new Test2();
        System.out.println(test2.property);
    }
}
1 голос
/ 19 июля 2010

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

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

...