Когда JVM будет хранить ссылки на переменные-члены в стеке? - PullRequest
0 голосов
/ 03 февраля 2019

Я читаю раздел 12.6.1 спецификаций Java SE и там говорится:

Могут быть разработаны оптимизирующие преобразования программы, которые уменьшают количество объектов, которыедостижимы, чтобы быть меньше, чем те, которые наивно будут считаться достижимыми.Например, Java-компилятор или генератор кода может выбрать установку переменной или параметра, который больше не будет использоваться для обнуления, чтобы в будущем хранилище для такого объекта могло быть потенциально восстановимо.

Другой пример этого происходитесли значения в полях объекта хранятся в регистрах.Программа может затем получить доступ к регистрам вместо объекта и никогда не получить доступ к объекту снова.Это будет означать, что объект является мусором.Обратите внимание, что такого рода оптимизация разрешена только в том случае, если ссылки находятся в стеке, а не хранятся в куче.

Соответствующий код:

class Foo {
    private final Object finalizerGuardian = new Object() {
        protected void finalize() throws Throwable {
            /* finalize outer Foo object */
        }
    }
} 

Мой вопрос такой:JVM будет хранить finalizerGuardian в стеке, а не в куче и почему?

Ответы [ 2 ]

0 голосов
/ 08 февраля 2019

Пример кода предназначен для иллюстрации последнего предложения вашего цитируемого текста: « Обратите внимание, что такого рода оптимизация разрешена, только если ссылки находятся в стеке, а не хранятся в куче », и этонемного странно, что вы сорвали его с поясняющего текста:

Например, рассмотрим шаблон Finalizer Guardian :

 class Foo {
     private final Object finalizerGuardian = new Object() {
         protected void finalize() throws Throwable {
             /* finalize outer Foo object */
         }
     }
 } 

Финализатор заставляет super.finalize вызываться, если подкласс переопределяет finalize и не вызывает явно super.finalize.

Если эти оптимизации разрешены для ссылок, которыесохраненный в куче, компилятор Java может обнаружить, что поле finalizerGuardian никогда не читается, обнулить его, немедленно собрать объект и вызвать финализатор на ранней стадии.Это противоречит цели: программист, вероятно, хотел вызвать финализатор Foo, когда экземпляр Foo стал недоступен.Поэтому такого рода преобразование недопустимо: внутренний объект класса должен быть достижимым до тех пор, пока доступен внешний объект класса.

Таким образом, пример кода иллюстрирует ограничение.«Оптимизирующие преобразования», упомянутые в спецификации, включают масштабирование объектов, примененное после того, как Escape Analysis доказал, что объект является чисто локальным, иными словами, оптимизируемый код охватывает весь срок жизни объекта.

Но это не так.не нужны такие местные объекты.Как уже упоминалось в спецификации, оптимизированный код может сохранять поля объекта в регистрах ЦП без необходимости их повторного чтения, таким образом, больше не нужно сохранять ссылку на объект.Аналогично, ссылочная переменная, все еще находящаяся в области видимости, может не использоваться.Если эта ссылка была единственной ссылкой на объект, удаление ее из оптимизированного кода позволяет выполнять более раннюю сборку мусора.

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

Как правило, могут быть получены произвольно большие графы объектовсобирается за один цикл сбора мусора, возможно, раньше, чем наивно ожидалось, или даже полностью оптимизируется.

0 голосов
/ 03 февраля 2019

классическим примером такого рода оптимизации (escape-анализа) является вычисление с Point class:


class Point {
    double x;
    double y;

    public Point(final double x, final double y) {
        this.x = x;
        this.y = y;
    }

    double length() {
        return Math.sqrt(x * x + y * y);
    }

    static double calc() {
        double result = 0;
        for (int i = 0; i < 100; i++) {
            // this allocation will be optimized 
            Point point = new Point(i, i);
            result += point.length();
        }
        return result;
    }
}

после встраивания этого new не потребуется, поскольку мы можем извлечь все поля влокальные переменные, такие как

Point point = new Point(i, i);
double x = point.x;
double y = point.y;
result += Math.sqrt(x * x + y * y);

->

Point point = new Point(i, i);
double x = i;
double y = i;
result += Math.sqrt(x * x + y * y);

, теперь очевидно, что new Point(i, i) бесполезен, и JIT просто удаляет эту строку.

Обратите внимание, что распределение было в стеке, т.е. в локальной переменной.Если бы оно было в поле, мы бы не смогли выполнить эту оптимизацию, потому что она хранится в куче.Вот как это работает.

о вашем фрагменте кода: finalizerGuardian всегда будет в поле (хранится в куче), и JVM ничего не сможет сделать с этим распределением.Более того, если класс Point из приведенного выше примера содержит такое поле, я думаю, что escape-анализ не может удалить выделение, поскольку это может изменить исходное поведение.

...