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

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

8 голосов
/ 17 сентября 2008

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

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

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

Ответы [ 6 ]

2 голосов
/ 17 сентября 2008

Правка, так как сначала я неправильно понял вопрос:

Цитируется отсюда http://www.memorymanagement.org/glossary/p.html:

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

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

1 голос
/ 13 октября 2011

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

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

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

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

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

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

1 голос
/ 17 сентября 2008

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

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

0 голосов
/ 24 февраля 2017

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

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

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

Ниже приведен класс, который я использую для такого рода вещей. Обратите внимание, что ссылки на 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;

    /**
     * This class provides a way for tracking the loss of reference of one type of
     * object to allow a secondary reference to be used to perform some cleanup
     * activity.  The most common use of this is with one object which might
     * contain or refer to another object that needs some cleanup performed
     * when the referer is no longer referenced.
     * <p>
     * An example might be an object of type Holder, which refers to or uses a
     * Socket connection.  When the reference is lost, the socket should be
     * closed.  Thus, an instance might be created as in
     * <pre>
     *    ReferenceTracker trker = ReferenceTracker() {
     *        public void released( Socket s ) {
     *            try {
     *                s.close();
     *            } catch( Exception ex ) {
     *                log.log( Level.SEVERE, ex.toString(), ex );
     *            }
     *        }
     *  };
     * 
* Где-то могут быть такие звонки, как следующие. *
     *        interface Holder {
     *            public T get();
     *        }
     *        class SocketHolder implements Holder {
     *            Socket s;
     *            public SocketHolder( Socket sock ) {
     *                s = sock;
     *            }
     *            public Socket get() {
     *                return s;
     *            }
     *        }
     * 
* Это определяет реализацию интерфейса Holder, который содержит * ссылка на объекты Socket. Использование trker * объект выше, может включать использование метода для создания * объекты и регистрация ссылок, как показано ниже. *
     *    public SocketHolder connect( String host, int port ) throws IOException {
     *        Socket s = new Socket( host, port );
     *        SocketHolder h = new SocketHolder( s );
     *        trker.trackReference( h, s );
     *        return h;
     *    }
     * 
* Программное обеспечение, желающее использовать сокетное соединение и передать его * используйте SocketHolder.get () для ссылки на экземпляр Socket во всех случаях. * затем, когда все ссылки на SocketHolder будут удалены, сокет * быть закрытым показанным методом released(java.net.Socket) * выше. *

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

0 голосов
/ 17 сентября 2008

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

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

...