Содержание

пятница, 17 августа 2012 г.

Как использовать фантомные ссылки

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

Основная задача фантомных ссылок как и метода finalize — дать возможность выполнить некоторый код, когда объект станет готов к сборке мусора. Большое отличие фантомных ссылок от других в том, что по ним нет возможности достучаться до объекта. Т.е понятие достижимости объекта по фантомной ссылке нельзя воспринимать в прямом смысле этого слова, так как объект недостижим по ней. Отсюда и главное преимущество по ставнению с методом – не возникает проблем реордеринга и нет возможности случайно воскресить объект, о которых можно подробнее почитать тут
Тем кто знаком с java.lang.ref API может показаться весьма не удобным работа с фантомными ссылками. Пример его использования для фантомных ссылок можно найти тут. Видимо, разработчикам HotSpot тоже так показалось и они добавили специальный класс sun.misc.Cleaner. Хотя он и находиться в проприетарном пакете, доступ к нему в отличие от sun.misc.Unsafe любезно открыт всем желающим. Чтобы повесить какое-то действие подчистку объекта, зарегистрировать некий Runnable следующим способом:
Object ref = new …;
Cleaner.create(ref, new Runnable() {
    @Override
    public void run() {
    // put your code here
    }
});
фантомные ссылки элегантно рассчитаны на то, что вам не удастся воскресить объект, ведь, передавая ваш Runnable в Cleaner вам придется всю необходимую информацию явно скопировать из объекта. Причем даже если программист ошибется и засунет туда ссылку на  объект, то он никогда не станет фантомно достижимым, так как ссылка на него всегда будет оставаться доступна через статический контекст класса Cleaner.

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

А имплементация Runnable как бы намекает программисту, что код который там присутствует будет выполняться в другом потоке, нежеле код других методов класса, что иногда забывается в случае метода finalize.

Не лишним будет так же упомянуть, что код вашего Runnable будет выполнять reference-handler потоком, что конечно бонус по сравнения с finalizer потоком, так как не требует JNI вызовов внутри VM, однако какую-то тяжелую обработку все же туда пихать не стоит, так как это может замедлить очистку других объектов.

Lifecycle у Cleaner будет такой же, как если вы бы писали обработку фантомной ссылки сами: при первой сборки мусора, когда объект стал достижи только фантомно сработает ваш код Runnable. И только при следующей сборке мусора объект будет реально удален. Если же вы переопределили метод finalize, то чтобы ваш объект реально собрался, потребуется как минимум три сборка мусора: во время первой выполниться код метода finalize, в последующей — ваш код Runnable и только в третьей сборке ваш объект освободит мест ов Heap.

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

Если же вам претит использовать проприетарное API HotSpot, то можете взглянуть в сторону FinalizableReference от apache, который тоже представляет некую обертку над фантомной ссылкой, облегчая работу с ними, причем финализирующий код в этом случае запускается сторонним потоком, а не потоком reference-handler или finalizer.

Ну и в заключении наконец скажу как вы неявно используете фантомные ссылки в повседневной жизни. Дело в том, что DirectByteBuffer, который широко используется в NIO или умельцами по запихиванию данных за пределы Heap, как раз и использует вышеописанный Cleaner для освобождения нативной памяти. Так же фантомными ссылками неявно пользуются разработчики, оперирующие через java с файлами замапленными в память с помощью MappedByteBuffer, который по сути и является DirectByteBuffer в HotSpot.