Почему постановка в очередь PhantomReference занимает больше циклов GC, чем WeakReference или SoftReference? - PullRequest
2 голосов
/ 21 июня 2019

Я решил продолжить https://stackoverflow.com/a/41998907/2674303 в отдельной теме.

Рассмотрим следующий пример:

public class SimpleGCExample {
    public static void main(String[] args) throws InterruptedException {
        ReferenceQueue<Object> queue=new ReferenceQueue<>();
        SimpleGCExample e = new SimpleGCExample();
        Reference<Object> pRef=new PhantomReference<>(e, queue),
                wRef=new WeakReference<>(e, queue);
        e = null;
        for(int count=0, collected=0; collected<2; ) {
            Reference ref=queue.remove(100);
            if(ref==null) {
                System.gc();
                count++;
            }
            else {
                collected++;
                System.out.println((ref==wRef? "weak": "phantom")
                        +" reference enqueued after "+count+" gc polls");
            }
        }
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalizing the object in "+Thread.currentThread());
        Thread.sleep(100);
        System.out.println("done finalizing.");
    }
}

Java 11 печатает следующее:

finalizing the object in Thread[Finalizer,8,system]
weak reference enqueued after 1 gc polls
done finalizing.
phantom reference enqueued after 3 gc polls

Первые 2 строки могут изменить порядок. Похоже, они работают параллельно.

Последний ряд иногда печатает 2 опроса gc, а иногда 3

Таким образом, я вижу, что включение PhantomReference занимает больше циклов GC. Как это объяснить? Это упомянуто где-то в документации (я не могу найти)?

приписка

Слабая ссылка Java документ:

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

PhantomReference java doc:

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

Разница мне не ясна

P.S. (Речь идет об объекте с нетривиальным методом финализации)

Я получил ответ на свой вопрос от @Holger:

Он (никакого сексизма, но я полагаю, что так) указал мне на java doc и заметил, что PhantomReference содержит дополнительную фразу по сравнению с «Мягкими и слабыми ссылками»:

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

Мой следующий вопрос был о том, что это значит он был завершен Я ожидал, что это означает, что метод finalize был завершен

Чтобы доказать это, я изменил приложение так:

public class SimpleGCExample {
    static SimpleGCExample object;

    public static void main(String[] args) throws InterruptedException {
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        SimpleGCExample e = new SimpleGCExample();
        Reference<Object> pRef = new PhantomReference<>(e, queue),
                wRef = new WeakReference<>(e, queue);
        e = null;
        for (int count = 0, collected = 0; collected < 2; ) {
            Reference ref = queue.remove(100);
            if (ref == null) {
                System.gc();
                count++;
            } else {
                collected++;
                System.out.println((ref == wRef ? "weak" : "phantom")
                        + " reference enqueued after " + count + " gc polls");
            }
        }
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalizing the object in " + Thread.currentThread());
        Thread.sleep(10000);
        System.out.println("done finalizing.");
        object = this;
    }
}

Я вижу следующий вывод:

weak reference enqueued after 1 gc polls
finalizing the object in Thread[Finalizer,8,system]
done finalizing.

И приложение зависает. Я думаю, это потому, что для слабых / мягких ссылок GC работает следующим образом: как только GC обнаруживает, что объект является слабым / мягким достижимым, он выполняет 2 действия параллельно :

  • включить Weak / Soft в зарегистрированный экземпляр ReferenceQueue
  • Выполнить метод финализации

Так что для добавления в ReferenceQueue не имеет значения, воскрес объект или нет.

Но для PhantomReference действия разные. Как только GC обнаружил, что объект является фантомно доступным, он последовательно выполняет следующие действия:

  • Выполнить метод финализации
  • Проверьте, что объект все еще только phantomReachable (убедитесь, что объект не был воскрешен во время выполнения метода finalize). И только если объект GC добавляет фантомную ссылку в ReferenceQueue

Но @Holger сказал, что оно было завершено означает, что JVM инициировала finalize () вызов метода и для добавления PhantomReference в ReferenceQueue не имеет значения, завершилось оно или нет. Но, похоже, мой пример показывает, что это действительно важно.

Честно говоря, я не понимаю разницы в соответствии с добавлением в RefernceQueue для слабой и мягкой ссылки.В чем была идея?

Ответы [ 2 ]

2 голосов
/ 24 июня 2019

Ключевым моментом является определение « фантомно достижимо » в документации пакета :

  • Объект - фантомдостижимый если он не является ни сильно, ни мягко, ни слабо достижимым, он был завершен , и к нему относится некоторая фантомная ссылка.

жирный акцент мой

Обратите внимание, что когда мы удаляем метод finalize(), фантомная ссылка собирается сразу же вместе со слабой ссылкой.

Это является следствием JLS §12.6 :

Для эффективности реализация может отслеживать классы, которые не переопределяют метод finalize класса Object, или переопределяет его тривиальным способом.

Мы призываем реализации рассматривать такие объекты как имеющие финализатор, который не переопределяется, и более эффективно их завершать, как описано в §12.6.1.

К сожалению, §12.6.1 не делаетпойти в следствиеИмеется в виду «наличие финализатора, который не переопределяется», но легко видеть, что реализация просто обрабатывает эти объекты как уже завершенные, никогда не ставит их в очередь для финализации и, следовательно, может немедленно их вернуть, что затрагивает большинствовсе объекты в типичных Java-приложениях.

Другая точка зрения состоит в том, что необходимые шаги для обеспечения того, чтобы в конечном итоге был вызван метод finalize(), т.е. создание и связывание экземпляра Finalizer, будут опущены.для объектов с тривиальным финализатором.Кроме того, исключая создание чисто локальных объектов после Escape Analysis, работает только для этих объектов.

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

¹ Хотя до Java 9 эта безопасность не была пуленепробиваемой, поскольку фантомные ссылки не очищались автоматически, а глубокие размышления позволяли извращать всю концепцию.

1 голос
/ 21 июня 2019

PhantomReference s будет поставлен в очередь только после того, как любое связанное finalizer завершит выполнение. Обратите внимание, что finalizer может воскресить объект (используется для хорошего эффекта бывшего Проекта безопасного интернета Принстона).

Точное поведение за пределами спецификации не указано. Здесь будут вещи, зависящие от реализации.

Так что же, похоже, происходит? Как только объект слабосборный, он также может быть завершен. Таким образом, WeakReference s может быть поставлен в очередь, а объекты поставлены в очередь для завершения в одном и том же событии остановки мира. Финал (-ы) завершения (-ий) работает (-ы) параллельно с вашим ReferenceQueue -оботом (основным). Следовательно, вы можете увидеть первые две строки вашего вывода в любом порядке, всегда (если не сильно задержано), за которым следует третья.

Только через некоторое время после выхода из вашего finalizer можно PhantomReference поставить в очередь. Следовательно, количество gc строго больше. Код выглядит как достаточно честная гонка. Возможно, изменение времени ожидания в миллисекундах изменит ситуацию. У большинства вещей GC нет точных гарантий.

...