Почему InvokeVirtual используется вместо InvokeSpecial при вызове class.NewInstance()?

Я смотрел в разборку следующей программы Java

public class ASMPlayground {
    private String bar;

    public String getBar(){
        return bar;
    }

    public void setBar(String bar) throws IllegalAccessException, InstantiationException {
        String name = String.class.newInstance();
        System.out.println(name);
    }

    public static void main(String[] args) {

    }
}

Следующий фрагмент байт-кода привлек мое внимание и показался неоптимальным

LDC Ljava/lang/String;.class
INVOKEVIRTUAL java/lang/Class.newInstance ()Ljava/lang/Object;
CHECKCAST java/lang/String

Вопрос:

InvokeVirtual используется, когда выполняемый метод зависит от ссылки на объект. Учитывая, что "Class" является окончательным, а newInstance() существует только в "Class", почему бы не использовать InvokeSpecial вместо InvokeVirtual? Разве это не было бы более производительным?

2 ответа

InvokeSpecial используется для обозначения вызовов, которые

суперкласс, частные и вызовы методов инициализации экземпляра

Spec: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html

Class.newInstance не является ни вызовом метода суперкласса, ни вызовом частного метода, ни вызовом метода инициализации. Это также не динамический метод, InvokeDynamic тут делать нечего. Обе инструкции не могут использоваться здесь, потому что это столкнулось бы с jvms. Во время создания jvms было возможно разрешить замену некоторых инструкций на "более производительные", но, как я вижу в jvms, это не было сделано.

Разве это не было бы более производительным?

JIT достаточно умен, чтобы понять, что в таких случаях нет необходимости просматривать таблицу виртуальных методов, поэтому он не должен замедлять выполнение. Фактические показатели должны сравниваться с реальными тестами, но я не вижу причин ожидать значительных различий там.

Тот факт, что целевой метод или класс final никогда не изменяет скомпилированную форму класса, используя другой класс / вызывая метод.

Это предписано JLS, глава 13. "Двоичная совместимость", §13.4.17, final методы:

Изменение метода, который объявлен final больше не быть объявленным final не нарушает совместимость с уже существующими двоичными файлами.

Точно так же §13.4.2. final Классы

Изменение класса, который объявлен final больше не быть объявленным final не нарушает совместимость с уже существующими двоичными файлами.

Очевидно, что инструкция вызова, основанная на целевом методе final природа нарушила бы эту спецификацию.

Нет ожидаемого влияния на производительность. Всегда возникают накладные расходы в первый раз, когда символьная ссылка должна быть преобразована в фактический метод (в терминах представления времени выполнения JVM). В этот момент JVM также может зафиксировать тот факт, что этот метод или его объявленный класс на самом деле final к инструкции вызова, если есть польза. Современные JVM идут еще дальше и, например, используют тот факт, что не final Метод на самом деле не был переопределен с тем же эффектом, хотя эти вызовы должны быть деоптимизированы, если загружается и создается экземпляр подкласса, который имеет переопределяющий метод. Таким образом, единственное отличие состоит в том, что final Модификатор гарантирует, что такая деоптимизация никогда не будет необходима.

Другие вопросы по тегам