Разъяснения по поводу Байт-кода и объектов

Я пишу Байт-код instrumenter. Прямо сейчас я пытаюсь узнать, как сделать это в присутствии объектов. Я хотел бы некоторые разъяснения по поводу двух строк, которые я считал в 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(), Я могу передать объект в качестве параметра методу, даже прежде, чем возвратиться из к основному ().

Кто-то мог подтвердить, что этот целый анализ верен? Спасибо за Ваше время.

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

Обновление

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

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

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

7
задан H-H 21 July 2010 в 17:27
поделиться

3 ответа

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

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

1
ответ дан 7 December 2019 в 05:16
поделиться

Вопреки тому, что определяет язык 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);
    }
}
3
ответ дан 7 December 2019 в 05:16
поделиться

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

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

РЕДАКТИРОВАТЬ: Раздел 4.9.4 JVMS гласит:

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

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


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

Из JVMS:

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

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

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

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

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

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

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


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



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

Обновлено:

Например,

  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

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

Во втором конструкторе метод для конструктора имеет метод

  • суперкласса , т. Е. вызов суперкласса конструктор (без метода arg), компилятор сгенерировал это по умолчанию потому что не было найдено явного super () как первое заявление.
  • байт-код для инициализации переменная экземпляра someint .
  • байт-код для остальной информации в тело конструктора.
4
ответ дан 7 December 2019 в 05:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: