Призрачные объекты

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

Мой вопрос: какой цели служит эта функция (объект не освобожден)?

(Единственная идея, которую я придумал, - позволить нативному коду выполнить посмертную очистку объекта, но это не очень убедительно).

6 ответов

Решение

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

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

Отредактируйте, так как сначала я неправильно понял вопрос:

Цитируется здесь http://www.memorymanagement.org/glossary/p.html:

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

Но я не нашел других ссылок, которые бы говорили то же самое.

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

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

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

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

Время от времени вы можете опрашивать ReferenceQueue и проверять, есть ли какие-либо новые PhantomReferences, чьи ссылочные объекты стали фантомно достижимыми. Чтобы иметь возможность что-нибудь полезное, можно, например, извлечь класс из java.lang.ref.PhantomReference, который ссылается на ресурсы, которые должны быть освобождены перед сборкой мусора. Указанный объект удаляется только после того, как фантомная ссылка сама становится недоступной.

http://www.javalobby.org/java/forums/m91822870.html

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

В частности, любой вид API, который раньше просто использовал объекты в памяти, но который вы переопределили, используя соединение с сокетом или файловое соединение с каким-либо другим, большим хранилищем резервных копий, может использовать PhantomReference для "закрытия" и очистки информации о соединении до объект был GC'd, и соединение никогда не закрывалось, потому что не было никакого интерфейса API управления жизненным циклом, который вы могли бы иначе использовать.

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

Ниже приведен класс, который я использую для такого рода вещей. Обратите внимание, что ссылки на PhantomReferences должны быть нелокальными ссылками для правильной работы. В противном случае jit заставит их преждевременно ставиться в очередь, прежде чем вы выйдете из блоков кода.

    import java.lang.ref.PhantomReference;
    import java.lang.ref.Reference;
    import java.lang.ref.ReferenceQueue;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.logging.Level;
    import java.util.logging.Logger;

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

* Примером может быть объект типа Holder, который ссылается на или использует * Разъем для подключения. Когда ссылка потеряна, сокет должен быть * закрыто. Таким образом, экземпляр может быть создан как в *

     * ReferenceTracker trker = ReferenceTracker () {
     * публичная пустота выпущена ( Socket s) {
     * пытаться {
     *                s.close();
     *            } catch( Exex ex) {
     * log.log (Level.SEVERE, ex.toString(), ex);
     *}
     *}
     *};
     * 
* Где-то могут быть такие звонки, как следующие. *
     * Держатель интерфейса {
     *            public T get();
     *        }
     * класс SocketHolder реализует Holder {
     * Розетки;
     *            public SocketHolder( Socket sock) {
     *                s = носок;
     *            }
     *            public Socket get() {
     * возврат с;
     *}
     *}
     * 
* Это определяет реализацию интерфейса Holder, который содержит * ссылка на объекты Socket. Использование trker * объект выше, может включать использование метода для создания * объекты и регистрация ссылок, как показано ниже. *
     * public SocketHolder connect (String host, int port) выдает IOException {
     *        Socket s = новый сокет (хост, порт);
     *        SocketHolder h = новый SocketHolder( s);
     *        trker.trackReference( h, s);
     * возврат ч;
     *}
     * 
* Программное обеспечение, желающее использовать сокетное соединение и передать его * используйте SocketHolder.get() для ссылки на экземпляр Socket во всех случаях. * затем, когда все ссылки на SocketHolder будут удалены, сокет * быть закрытым released(java.net.Socket) показанный метод * выше. *

* Класс {@link ReferenceTracker} использует {@link PhantomReference} для первого аргумента в качестве * ключа к карте, содержащей ссылку на второй аргумент. Таким образом, когда экземпляр ключа * освобождается, ссылка на ключ ставится в очередь, может быть удалена из * очереди и использована для удаления значения из карты, которое затем передается в * release (). */ public abstract class ReferenceTracker { /** * Экземпляр потока, который удаляет записи из ссылочной очереди, refqueue по мере их появления. * / частный волатильный опрос RefQueuePoll; /** * Экземпляр Logger, используемый для этого экземпляра. Это имя будет включать суффикс *, если этот конструктор используется. */ private static final Logger log = Logger.getLogger(ReferenceTracker.class.getName()); /** * Имя, указывающее, какой это экземпляр для ведения журнала и других необходимых * разделений. * / частная финальная строка, которая; / ** * Создает новый экземпляр ReferenceTracker, используя переданное имя, чтобы дифференцировать * экземпляр в журналировании и реализации toString(). * @param which Имя этого экземпляра для различения нескольких экземпляров при ведении журнала и т. д. */ public ReferenceTracker( String which) { this.which = which; } /** * Создает новый экземпляр ReferenceTracker без соответствующего имени. */ public ReferenceTracker() { this.which = null; } /** * Предоставляет доступ к имени этого экземпляра. * @return Название этого экземпляра. */ @Override public String toString() { if( which == null) { return super.toString()+": ReferenceTracker"; } return super.toString()+": ReferenceTracker["+which+"]"; } /** * Подклассы должны реализовывать этот метод. Он будет вызван, когда все ссылки на * связанный объект держателя будут удалены. * @param val Значение, передаваемое в качестве второго аргумента соответствующему вызову {@link #trackReference(Object, Object) trackReference(T,K)} */ public abstract void release ( K val); /** Очередь ссылок для ссылок на объекты-держатели */ private final ReferenceQueuerefqueue = new ReferenceQueue(); /** * Счетчик общего количества потоков, которые были созданы и затем уничтожены, поскольку записи * были отслежены. При отсутствии отслеживаемых ссылок очередь не выполняется. */ private final AtomicInteger tcnt = new AtomicInteger(); частный изменчивый логический запуск; / ** * Реализация потока, которая опрашивает {@link #refqueue} для последующего вызова {@link release (K)} *, поскольку ссылки на объекты T отбрасываются. * / закрытый класс RefQueuePoll расширяет поток {/ ** * номер потока, связанный с этим экземпляром. Вкратце может существовать два экземпляра * этого класса, который существует в энергозависимой системе. Если это так, это значение * будет отображаться в некоторых журналах, чтобы различать активные. */ private final int mycnt; /** * Создает экземпляр этого класса. */ public RefQueuePoll() { setDaemon( true); setName( getClass().getName()+": ReferenceTracker ("+which+")"); mycnt = tcnt.incrementAndGet(); } /** * Этот метод обеспечивает всю активность выполнения refqueue.remove() * звонки, а затем звонки released(K) чтобы приложение высвободило необходимые * ресурсы. */ public @Override void run() { try { doRun(); } catch( Throwable ex) { log.log(сделано? Level.INFO: Level.SEVERE, ex.toString()+": остановка потока опроса phantom ref", ex); } наконец {работает = ложь; } } private volatile boolean done = false; private void doRun() { while(!done) {Ссылка ref = null; try { running = true; ref = refqueue.remove(); K ctl; синхронизированный ( refmap) { ctl = refmap.remove( ref); done = actCnt.decrementAndGet() == 0; if( log.isLoggable( Level.FINE)) { log.log(Level.FINE, "current act refs={0}, mapsize={1}", new Object[]{actCnt.get(), refmap.size()}); } if( actCnt.get()!= refmap.size()) { Throwable ex = new IllegalStateException("количество активных ссылок и размер карты не синхронизированы"); log.log(Level.SEVERE, ex.toString(), ex); } } if( log.isLoggable( Level.FINER)) { log.log(Level.FINER, "ссылка выпущена для: {0}, dep={1}", новый объект []{ref, ctl}); } if( ctl!= null) { try { release ( ctl); if( log.isLoggable( Level.FINE)) { log.log(Level.FINE, "освобожденный зависимый объект: {0}", ctl); } } catch( RuntimeException ex) { log.log( Level.SEVERE, ex.toString(), ex); } } } catch( Exception ex) { log.log( Level.SEVERE, ex.toString(), ex); } наконец { if( ref!= null) { ref.clear(); } } } if( log.isLoggable( Level.FINE)) { log.log(Level.FINE, "завершение потока опросов {0} для {1}", новый объект []{mycnt, this}); } } } /** * Количество активных ссылок. */ private final AtomicInteger actCnt = new AtomicInteger(); /** * Отображение из T Ссылки на K объектов, которые будут использоваться для освобожденного (K) вызова */ private final ConcurrentHashMap,K>refmap = new ConcurrentHashMap,K>(); /** * Добавляет отслеживаемую ссылку. dep не должен ссылаться на ref каким-либо образом, за исключением, возможно, * WeakReference. Деп почти всегда что-то упоминается в ссылке. * @throws IllegalArgumentException из ref и dep - это один и тот же объект. * @param dep Зависимый объект, который нуждается в очистке, когда ref больше не ссылается. * @param ref объект, ссылка на который должна отслеживаться */ public void trackReference( T ref, K dep) { if( ref == dep) { throw new IllegalArgumentException( "Ссылочный объект и зависимый объект не могут быть одинаковыми"); } PhantomReference p = new PhantomReference( ref, refqueue); синхронизированный ( refmap) { refmap.put( p, dep); if( actCnt.getAndIncrement() == 0 || running == false) { if( actCnt.get() > 0 && running == false) { if (log.isLoggable(Level.FINE)) { log.fine("запуск остановленного фантомного опроса темы опроса"); } } poll = new RefQueuePoll(); poll.start(); if( log.isLoggable( Level.FINE)) { log.log( Level.FINE, "поток опроса #{0} создан для {1}", новый объект []{tcnt.get(), this}); } } } } /** * Этот метод может быть вызван, если JVM, в которой находится трекер, * выключен, или другой контекст отключается, и объекты, отслеживаемые * трекером, теперь должны быть освобождены. Этот метод приведет к вызову * {@link #released(Object) release (K)} для каждой выдающейся ссылки. */ public void shutdown() {Листрем; // Скопируйте значения и очистите карту, чтобы // освобожденный вызывался только один раз, в случае, если GC позже удаляет ссылки синхронизированные ( refmap) { rem = new ArrayList( refmap.values ​​()); refmap.clear(); } for( K dep: rem) { try { release ( dep); } catch( Exception ex) { log.log( Level.SEVERE, ex.toString(), ex); } } } }

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

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

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