Байт-код конструктора

В руководстве по ASM рассказывается о конструкторах:

package pkg;
public class Bean {
  private int f;
  public int getF() {
      return this.f;
  }
  public void setF(int f) {
      this.f = f;
  }
}

Класс Bean также имеет общедоступный конструктор по умолчанию, который генерируется компилятором, поскольку программист не определил явный конструктор. Этот общедоступный конструктор по умолчанию создается как Bean() { super(); }. Байт-код этого конструктора следующий:

ALOAD 0
INVOKESPECIAL java/lang/Object <init> ()V
RETURN

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

Как значение this уже известно JVM до первой инструкции конструктора?


person rapt    schedule 09.11.2018    source источник


Ответы (2)


Первое, что нужно понять, это то, как работает создание объектов на уровне байт-кода.

Как поясняется в JVMS, §3.8. Работа с экземплярами класса:

Экземпляры класса виртуальной машины Java создаются с помощью инструкции new виртуальной машины Java. Напомним, что на уровне виртуальной машины Java конструктор выглядит как метод с именем, предоставленным компилятором <init>. Этот метод со специальным названием известен как метод инициализации экземпляра (§2.9). Для данного класса может существовать несколько методов инициализации экземпляра, соответствующих нескольким конструкторам. После создания экземпляра класса и инициализации его переменных экземпляра, включая переменные класса и всех его суперклассов, значениями по умолчанию, вызывается метод инициализации экземпляра нового экземпляра класса. Например:

   Object create() {
       return new Object();
   }

компилируется в:

   Method java.lang.Object create()
   0   new #1              // Class java.lang.Object
   3   dup
   4   invokespecial #4    // Method java.lang.Object.<init>()V
   7   areturn

Таким образом, вызов конструктора через invokespecial разделяет поведение передачи this в качестве первого аргумента с invokevirtual.

Однако следует подчеркнуть, что ссылка на неинициализированную ссылку обрабатывается особым образом, поскольку вам не разрешено использовать ее до вызова конструктора (или суперконструктора, когда вы находитесь внутри конструктора). Это обеспечивается верификатором.

JVMS, §4.10.2.4. Instance Initialization Methods and Newly Created Objects:

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

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

Точно так же специальный тип создается и помещается в модель стека операндов верификатора в результате инструкции виртуальной машины Java new. Специальный тип указывает инструкцию, с помощью которой был создан экземпляр класса, и тип созданного неинициализированного экземпляра класса. Когда метод инициализации экземпляра, объявленный в классе неинициализированного экземпляра класса, вызывается для этого экземпляра класса, все вхождения специального типа заменяются предполагаемым типом экземпляра класса. Это изменение типа может распространяться на последующие инструкции по мере продолжения анализа потока данных.

Таким образом, код, создающий объект с помощью инструкции new, не может использовать его каким-либо образом до вызова конструктора, тогда как код конструктора может только назначать поля до вызова другого (this(…) или super(…)) конструктора. (возможность, используемая внутренними классами для инициализации ссылки на внешний экземпляр в качестве первого действия), но по-прежнему ничего не может сделать со своим неинициализированным this.

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

Обычно рекомендуется прочитать Спецификацию виртуальной машины Java® (соответственно его версия Java 11) вместе с к любой конкретной документации или руководствам по ASM.

person Holger    schedule 09.11.2018

На уровне JVM сначала объект выделяется, деинициализируется, затем для этого объекта вызывается конструктор. Конструктор — это более или менее метод экземпляра, выполняемый для неинициализированного объекта.

Даже в языке Java this существует и имеет все свои поля в первой строке конструктора.

person Louis Wasserman    schedule 09.11.2018
comment
Разве весь этот более ранний процесс, который вы описали, не происходит как байт-код? - person rapt; 09.11.2018
comment
@rapt Да, но не в самом конструкторе. Это происходит в коде, который вызывает конструктор. - person Louis Wasserman; 09.11.2018