Статьи

Слабый, слабый, слабый, использующий сборщик мусора с рекомендациями специалистов

Когда и когда не следует использовать ссылки специалистов в Java

Слабые ссылки Soft и Phantom опасны и сильны. Если они используются неправильно, они могут разрушить производительность JVM; однако при правильном использовании они могут существенно повысить производительность и четкость программы.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
Object y=new Object();
// y is a hard reference to the object
// and so that object cannot be reclaimed.
 
Obejct x=WeakReference<Object>(y);
// now x is a weak reference to the object
// (not to y - as y is just a variable).
// The object still cannot be reclaimed
// because y is still a hard reference to it.
 
y=null;
// now there is only a weak reference to
//the object, it is eligible for garbage collection.
 
if(x.get()==null){
    System.out.println("The object has gone away");
}else{
    System.out.println("The object is " + x.get().toString());
}

Вы заметили умышленную ошибку? Его легко пропустить, и он, вероятно, не будет отображаться при юнит-тестировании. Это именно тот вопрос, который заставляет меня сказать:

Используйте слабые / мягкие ссылки только в том случае, если это необходимо, а возможно, даже тогда.

Когда JVM находится под давлением памяти, он может вернуть объект между первым и вторым вызовами метода get в слабой ссылке. Это приведет к тому, что программа вызовет исключение нулевого указателя, когда метод toString вызывается с нулевым значением. Правильная форма для кода:

1
2
3
4
5
6
7
8
Object x=x.get();
// Now we have null xor a hard reference to
// the object
if(z==null){
    System.out.println("The object has gone away");
}else{
    System.out.println("The object is " + z.toString());
}

Таким образом, они безумны, плохи и опасны, чтобы быть с ними; почему мы хотим их?

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

Это работает так:

У нас есть некоторые данные, например, данные о клиентах, которые хранятся в базе данных. Мы продолжаем искать это, и это медленно. Что мы можем сделать, это кэшировать эти данные в оперативной памяти. Однако, в конечном счете, RAM заполняется именами и адресами, и JVM выдает OutOfMemoryError. Решение состоит в том, чтобы хранить имена и адреса в объектах, которые только слабо достижимы. Что-то вроде этого:

1
2
3
4
5
6
ConcurrentHasMap>String,WeakReference>CustomerInfo<< cache=new ConcurrentHashMap><();
...
CustomerInfo currentCustomer=cache.get(customerName);
if(currentCustomer==null){
    currentCustomer=reloadCachesEntry(customerName);
}

Эта невинная маленькая модель вполне способна поставить JVM размером с монстра на колени. Шаблон использует сборщик мусора JVM для управления кэшем в памяти. Сборщик мусора никогда не был предназначен для этого. Шаблон злоупотребляет сборщиком мусора, заполняя память слабо достижимыми объектами, которые запускают JVM из пространства кучи. Когда JVM не хватает памяти, он должен пройти все ссылки, слабые, мягкие и другие, в свою кучу и восстановить ОЗУ. Это дорого и показывает стоимость обработки. Это даже хуже на очень больших экземплярах JVM с большим количеством процессорных ядер, потому что сборщик мусора вполне может в конечном итоге выполнить полный цикл «останови мир» и, следовательно, снизить производительность до уровня одного ядра!

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

Слабый против мягкого

в чем разница? Ну, там действительно много. В некоторых JVM (например, в JVM клиентского хоста, но это может измениться в любое время) слабые ссылки помечаются для преимущественной сборки мусора. Другими словами, сборщик мусора должен приложить больше усилий для восстановления памяти из графа объектов, на который они ссылаются (и не ссылаются на мягкие или жесткие ссылки), чем для другой памяти. Мягкие ссылки не имеют этой идеи для них. Тем не менее, на некоторых JVM это просто необязательная идея, на которую нельзя положиться, и в любом случае это плохая идея. Я бы предложил все время использовать мягкие или слабые ссылки и придерживаться этого. Выберите, какой вам нравится звук. Я предпочитаю имя WeakReference, поэтому склонен использовать это.

Есть еще одно отличие; объект, на который ссылается мягкая ссылка и слабая ссылка, но не жесткая ссылка, может иметь ситуацию, в которой он все еще может быть получен из метода .get () слабой ссылки, но не метода мягкой ссылки. Обратное невозможно, а не наоборот. Код, основанный на таком поведении, вероятно, ошибочен.

Хорошо использует для слабых ссылок

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

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

Автоматическое уведомление о рекламации

Иногда нам может потребоваться сообщить, когда слабая или мягкая (или другая разновидность — фантомная) ссылка была восстановлена. Это можно сделать через систему очередей. Мы можем сделать это, используя справочную очередь:

1
WeakReference(T referent, ReferenceQueue<? super T> q)

Мы делаем что-то вроде этого:

01
02
03
04
05
06
07
08
09
10
11
12
ReferenceQueue junkQ = new ReferenceQueue<>();
....
WeakReference<FileSlot> mySlot=new WeakReference<>(aSlot);
....
// In a different thread - make sure it is daemon!
WeakReference<FileSlot> isDead;
while(true){
    isDead = junkQ.remove();
    // Take some action based on the fact it is dead
    // But - it might not be dead - see end of post 🙁
...
}

Но, помните, к тому времени, когда слабая ссылка заканчивается на junkQ, вызов .get () для него вернет ноль. Если вам придется хранить информацию, чтобы какое-либо действие вам было интересно, оно может происходить где-то еще (например, ConcurrentHashMap, где ссылка является ключом),

Так что же такое фантом?

Призрачные ссылки — это тот вид, который, когда они вам нужны, вам действительно нужны. Но на первый взгляд они кажутся совершенно бесполезными. Видите ли, всякий раз, когда вы вызываете .get () для фантомной ссылки, вы всегда получаете нуль назад. Невозможно использовать фантомную ссылку, чтобы добраться до объекта, к которому она относится — никогда. Ну, это не совсем так. Иногда мы можем достичь этого через JNI, но мы никогда не должны этого делать.

Рассмотрим ситуацию, когда вы выделяете собственную память в JNI, связанную с объектом Java. Это модель, которую DirectBuffers использует в пакете noi JDK. Это то, что я неоднократно использовал в крупных коммерческих проектах.

Итак, как нам вернуть ту родную память?

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

Мы могли бы поместить код в финализатор, который вызывает код JNI для восстановления памяти. Это плохо (как в случае со смертельным исходом), потому что JVM почти гарантируют, что они будут вызывать финализаторы. Так что не делай этого! Но на помощь приходят призрачные ссылки! Во-первых, нам нужно понять «фантомную достижимость»: фантомная ссылка ставится в очередь только в том случае, если вещь, к которой она относится, не может быть достигнута с помощью какой-либо другой ссылки (твердой, слабой или мягкой). В этот момент ссылка на фантом может быть поставлена ​​в очередь. Если у объекта был финализатор, он будет либо проигнорирован, либо запущен; но это не вернуло бы объект к жизни. Фантомно достижимые объекты «безопасны» для собственного кода JNI (или любого другого кода) для восстановления ресурсов.

Так что наш код с фантомными ссылками может выглядеть так:

01
02
03
04
05
06
07
08
09
10
11
ReferenceQueue<FileSlot> junkQ = new ReferenceQueue<>();
....
Phantom<FileSlot> mySlot=new Phantom<>(aSlot);
....
// In a different thread - make sure it is daemon!
Phantom<FileSlot> isDead;
while(true){
    isDead=junkQ.remove();
    long handle=lookUpHandle(isDead);
    cleanNativeMemory(handle);
}

В этом паттерне мы сохраняем дескриптор, который может использовать нативный код для поиска и восстановления ресурсов в структуре (возможно, в другой хэш-карте) в Java. Когда мы абсолютно уверены, что Java-объект не может быть возвращен к жизни — он доступен фантомно (т. Е. Является призраком), тогда мы можем безопасно вернуть нативный ресурс. Если ваш код выполняет другие «непослушные» вещи, используя sun.misc.unsafe (для пример) этот прием также может быть полезен. Для полного примера, который использует эту технику — проверьте этот пост .

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

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

Ссылка: Слабая, слабая, слабая, использование сборщика мусора со ссылками специалистов от нашего партнера по JCG Александра Тернера в блоге Java Advent Calendar .