作者 陈彩华
文章转载交流请联系 caison@aliyun.com
В этой статье анализируются концепции и сценарии использования сильных ссылок, мягких ссылок, слабых ссылок и виртуальных ссылок путем анализа эталонной модели в Java.знать это и знать это, я надеюсь предоставить справку для всех в реальной практике разработки и изучения проектов с открытым исходным кодом.
1 Ссылка на Java
Для механизма сборки мусора в Java необходимость сбора объекта зависит от того, имеется ли ссылка на объект или нет. Таким образом, ссылки также являются важной концепцией управления памятью JVM. В Java JVM отвечает за выделение и утилизацию памяти, в этом ее преимущество (удобство использования, и программе больше не нужно заботиться о памяти, как в языке Си), но и недостаток (недостаточно гибкая). Таким образом, Java предоставляет модель эталонной иерархии, которая можетОпределите важность и приоритет объектов Java для повышения эффективности восстановления памяти JVM..
Что касается определения ссылки, до JDK1.2, если значение, хранящееся в данных ссылочного типа, представляет собой начальный адрес другой части памяти, это называется, что эта часть памяти представляет ссылку; после JDK1.2 ссылка Java к ссылке Концепция ссылки была расширена, и ссылка разделена на четыре типа: Сильная ссылка, Мягкая ссылка, Слабая ссылка и Фантомная ссылка.
В основном используются мягкие эталонные объекты и слабые объекты приложений: когда места в памяти достаточно, их можно хранить в памяти; если места в памяти после сборки мусора все еще очень мало, эти объекты можно отбросить. Кэш-функция многих систем подходит для этого варианта использования.
Виртуальный эталонный объект используется для замены ненадежного метода финализации и может получать событие повторного использования объекта для очистки ресурсов.
2 Жизненный цикл объекта
2.1 Жизненный цикл неклассифицированного эталонного объекта
Как упоминалось ранее, иерархическая эталонная модель используется для повторного использования памяти.Без иерархических эталонных объектов жизненный цикл объекта от создания до повторного использования можно просто суммировать на следующем рисунке: объект создается, используется, может быть собран, Наконец, заштрихованная область указывает, когда объект «сильно доступен»:
2.2 Жизненный цикл иерархического эталонного объекта
После того, как пакет java.lang.ref был представлен в JDK1.2, жизненный цикл объектов имеет еще три стадии: мягкую доступность, слабую доступность и виртуальную доступность Эти состояния применимы только к объектам, которые удовлетворяют условиям сборки мусора. Эти объекты находятся в стадии Это не является строгой эталонной стадией, и их необходимо указать на основе соответствующего класса эталонных объектов в пакете java.lang.ref.
-
мягкий охват Мягкодоступные объекты указываются SoftReference, а сильных ссылок нет.Сборщик мусора будет хранить объект как можно дольше, но соберет его, прежде чем выдать исключение OutOfMemoryError.
-
слабый охват Слабо достижимые объекты обозначаются WeakReference, а сильных или мягких ссылок нет.Сборщик мусора переработает объект в любое время и не будет пытаться его сохранить, а соберет его, прежде чем выдать исключение OutOfMemoryError.
Предположим, что сборщик мусора в какой-то момент времени определяет, что объект слабо достижим. В этот момент он атомарно очистит объект, связанный со слабо достижимым объектом, на который ссылаются.
- виртуальный охват Фантомно-достижимый объект указывается с помощью PhantomReference, он помечен для сборки мусора, и его финализатор (если есть) запущен. В этом контексте термин «достижимый» на самом деле является неправильным, поскольку у вас нет доступа к фактическому объекту.
Появление трех новых необязательных состояний на диаграмме жизненного цикла объекта может вызвать некоторую путаницу. Логический порядок — от сильного к мягкому, слабому и виртуальному и, наконец, к переработке, но фактическая ситуация зависит от эталонных объектов, созданных программой. Но если WeakReference создается, а SoftReference не создается, объект переходит непосредственно от сильного к слабому, к окончательному и к коллекции.
3 сильные цитаты
Строгая ссылка означает повсеместное присутствие в коде программы. Например, obj и str в следующем коде являются сильными ссылками:
Object obj = new Object();
String str = "hello world";
Пока сильная ссылка все еще существует, сборщик мусора никогда не вернет объект, на который указывает ссылка, и даже в случае нехватки памяти JVM не вернет объект, даже если выдаст исключение OutOfMemoryError.
При фактическом использовании связь между объектом и строгой ссылкой может быть прервана путем явного присвоения ссылке значения null.Если нет ссылки для выполнения объекта, сборщик мусора утилизирует объект в соответствующее время.
Например, метод удаления класса ArrayList реализует очистку, присваивая ссылке значение null:
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
4 Справочные объекты
Прежде чем вводить мягкие ссылки, слабые ссылки и виртуальные ссылки, необходимо ввести ссылочные объекты. Ссылочный объект — это уровень косвенности между программным кодом и другими объектами, называемый ссылочным объектом. Каждый ссылочный объект строится вокруг ссылки на объект, и ссылочное значение не может быть изменено.
Ссылочный объект предоставляет get() для получения строгой ссылки на его ссылочное значение, и сборщик мусора может в любое время вернуть объект, на который указывает ссылочное значение. Как только объект будет переработан, метод get() вернет значение null.Чтобы правильно использовать эталонный объект, используйте SoftReference в качестве справочного примера ниже:
/**
* 简单使用demo
*/
private static void simpleUseDemo(){
List<String> myList = new ArrayList<>();
SoftReference<List<String>> refObj = new SoftReference<>(myList);
List<String> list = refObj.get();
if (null != list) {
list.add("hello");
} else {
// 整个列表已经被垃圾回收了,做其他处理
}
}
То есть при использовании:
- 1. Вы всегда должны проверять, является ли эталонное значение нулевым Ссылочные объекты могут быть утилизированы сборщиком мусора в любое время, и рано или поздно вы получите исключение NullPointerException, если опрометчиво используете ссылочное значение.
- 2. Должна использоваться сильная ссылка, чтобы указать на значение, возвращаемое ссылочным объектом. Сборщик мусора может удалить указанный объект в любое время, даже в середине выражения.
/**
* 正确使用引用对象demo
*/
private static void trueUseRefObjDemo(){
List<String> myList = new ArrayList<>();
SoftReference<List<String>> refObj = new SoftReference<>(myList);
// 正确的使用,使用强引用指向对象保证获得对象之后不会被回收
List<String> list = refObj.get();
if (null != list) {
list.add("hello");
} else {
// 整个列表已经被垃圾回收了,做其他处理
}
}
/**
* 错误使用引用对象demo
*/
private static void falseUseRefObjDemo(){
List<String> myList = new ArrayList<>();
SoftReference<List<String>> refObj = new SoftReference<>(myList);
// XXX 错误的使用,在检查对象非空到使用对象期间,对象可能已经被回收
// 可能出现空指针异常
if (null != refObj.get()) {
refObj.get().add("hello");
}
}
- 3. Должна содержать сильную ссылку на указанный объект Если вы создаете объект-ссылку, не удерживая сильную ссылку на объект, то сам объект-ссылка будет собран сборщиком мусора.
- 4. Когда на ссылочное значение не указывают другие сильные ссылки, играют роль мягкие ссылки, слабые ссылки и виртуальные ссылки Существование ссылочных объектов облегчает отслеживание и эффективную сборку мусора.
5 Мягкие ссылки, слабые ссылки и фантомные ссылки
Три важных класса реализации ссылочных объектов расположены в пакете java.lang.ref: мягкая ссылка SoftReference, слабая ссылка WeakReference и виртуальная ссылка PhantomReference.
5.1 Мягкие ссылки
Мягкие ссылки используются для описания полезных, но не обязательных объектов. Для объектов, связанных с программными ссылками, прежде чем система выдаст исключение OutOfMemoryError, эти объекты будут включены в диапазон восстановления для второго восстановления. Если для этой коллекции недостаточно памяти, будет выдано исключение OutOfMemoryError. После JDK1.2 класс SoftReference предоставляется для реализации мягких ссылок.
Вот пример использования:
import java.lang.ref.SoftReference;
public class SoftRefDemo {
public static void main(String[] args) {
SoftReference<String> sr = new SoftReference<>( new String("hello world "));
// hello world
System.out.println(sr.get());
}
}
Это упоминается в документации JDK: Мягкие ссылки подходят для кэшей, чувствительных к памяти: доступ к каждому кэшированному объекту осуществляется через SoftReference, и если JVM решит, что требуется место в памяти, она очистит и вернет некоторые или все объекты, соответствующие к мягким ссылкам. Если место не требуется, SoftReference указывает, что объект остается в куче и к нему можно получить доступ из программного кода. В этом случае на них строго ссылаются, когда они активно используются, и мягко ссылаются в противном случае. Если мягкие ссылки очищены, кеш необходимо очистить.
На практике, если кэшированные объекты не очень велики, порядка нескольких килобайт каждый, стоит рассмотреть объекты мягких ссылок. Например: реализация файлового сервера, который должен периодически извлекать одни и те же файлы или кэшировать графы больших объектов. Если объект небольшой и многие объекты должны быть очищены, чтобы оказать влияние, это не рекомендуется, поскольку очистка объектов мягких ссылок увеличивает нагрузку на общий процесс.
5.2 Слабые ссылки
Слабые ссылки также используются для описания несущественных объектов, но их сила слабее, чем у мягких ссылок.Объекты, связанные со слабыми ссылками, могут существовать только до тех пор, пока не будет отправлена следующая сборка мусора.Когда сборщик мусора работает, объекты, связанные только со слабыми ссылками, будут утилизированы независимо от того, достаточно ли текущей памяти или нет..
После JDK1.2 для реализации слабых ссылок предоставляется класс WeakReference.
/**
* 简单使用弱引用demo
*/
private static void simpleUseWeakRefDemo(){
WeakReference<String> sr = new WeakReference<>(new String("hello world " ));
// before gc -> hello world
System.out.println("before gc -> " + sr.get());
// 通知JVM的gc进行垃圾回收
System.gc();
// after gc -> null
System.out.println("after gc -> " + sr.get());
}
Вы можете видеть, что объекты, связанные со слабыми ссылками, перерабатываются после gc. Интересно то, что если вы поместите приведенный выше код:
WeakReference<String> sr = new WeakReference<>(new String("hello world "));
изменить на
WeakReference<String> sr = new WeakReference<>("hello world ");
программа будет выводить
before gc -> hello world
after gc -> hello world
Это связано с тем, что разница между прямым использованием присваивания строк Java и использованием new заключается в следующем:
- new создает объект в куче, который можно перерабатывать в обычном режиме.
- Непосредственное присвоение строки, например: String str = String("Hello"); JVM сначала просматривает пул строк, чтобы увидеть, найдена ли строка «Hello», находит ее и ничего не делает; В противном случае создайте новый объект String и поместите его в пул констант String (пул констант до Hotspot 1.7 хранится в бессмертном поколении, а версии после Hotspot 1.7 и 1.7 хранятся в области кучи, которая обычно не перерабатывается гк). В то же время, поскольку new встречается, объект String будет создан в памяти (не в пуле констант String) для хранения «Hello», а объект String в памяти (не в пуле String) будет возвращен в str .
WeakHashMapЧтобы упростить использование слабых ссылок, Java также предоставляет WeakHashMap, который похож по функциям на HashMap. Внутренняя реализация заключается в том, чтобы обернуть ключ слабыми ссылками. Когда ключевой объект не имеет сильных ссылок, gc автоматически повторно использовать объекты ключей и значений.
/**
* weakHashMap使用demo
*/
private static void weakHashMapDemo(){
WeakHashMap<String,String> weakHashMap = new WeakHashMap<>();
String key1 = new String("key1");
String key2 = new String("key2");
String key3 = new String("key3");
weakHashMap.put(key1, "value1");
weakHashMap.put(key2, "value2");
weakHashMap.put(key3, "value3");
// 使没有任何强引用指向key1
key1 = null;
System.out.println("before gc weakHashMap = " + weakHashMap + " , size=" + weakHashMap.size());
// 通知JVM的gc进行垃圾回收
System.gc();
System.out.println("after gc weakHashMap = " + weakHashMap + " , size="+ weakHashMap.size());
}
Вывод программы:
before: gc weakHashMap = {key1=value1, key2=value2, key3=value3} , size=3
after: gc weakHashMap = {key2=value2, key3=value3} , size=2
WeakHashMap больше подходит для сценариев кэширования, таких как кэш Tomcat.
5.3 Справочная очередь
Прежде чем вводить виртуальные ссылки, давайте представим очередь ссылок: При использовании ссылочных объектов не очень эффективно определять, был ли объект возвращен, оценивая, является ли значение, возвращаемое методом get(), нулевым, особенно когда у нас много ссылочных объектов, если мы хотим узнать, какие объекты были возвращены. Для того, чтобы быть переработанным, все объекты должны быть пройдены.
Лучшим решением является использование ссылочной очереди, которая связана с очередью при создании ссылочного объекта.Когда gc (поток сборки мусора) готовится вернуть объект, если он обнаруживает, что у него все еще есть только мягкие ссылки (или слабые ссылки, или виртуальные ссылки), указывающие на него, мягкая ссылка (или слабая ссылка, или виртуальная ссылка) будет добавлена в связанную очередь ссылок (ReferenceQueue) перед повторным использованием объекта.
Если сам объект мягкой ссылки (или слабой ссылки, или виртуальной ссылки) находится в очереди ссылок, это означает, что объект, на который указывает объект ссылки, был переработан., поэтому, чтобы узнать все объекты, которые были переработаны, просто пройдитесь по эталонной очереди.
Когда объект, на который указывает мягкая ссылка (или слабая ссылка, или виртуальная ссылка) объект, истребуется, то сам объект ссылки не имеет значения, если таких объектов в программе большое количество (обратите внимание, что мягкая ссылка, мы создали слабую ссылку Ссылка и виртуальный объект ссылки сами по себе являются сильной ссылкой и не будут автоматически восстановлены gc), что приводит к трате памяти. Таким образом, мы можем вручную повторно использовать сам эталонный объект в очереди ссылок.
/**
* 引用队列demo
*/
private static void refQueueDemo() {
ReferenceQueue<String> refQueue = new ReferenceQueue<>();
// 用于检查引用队列中的引用值被回收
Thread checkRefQueueThread = new Thread(() -> {
while (true) {
Reference<? extends String> clearRef = refQueue.poll();
if (null != clearRef) {
System.out
.println("引用对象被回收, ref = " + clearRef + ", value = " + clearRef.get());
}
}
});
checkRefQueueThread.start();
WeakReference<String> weakRef1 = new WeakReference<>(new String("value1"), refQueue);
WeakReference<String> weakRef2 = new WeakReference<>(new String("value2"), refQueue);
WeakReference<String> weakRef3 = new WeakReference<>(new String("value3"), refQueue);
System.out.println("ref1 value = " + weakRef1.get() + ", ref2 value = " + weakRef2.get()
+ ", ref3 value = " + weakRef3.get());
System.out.println("开始通知JVM的gc进行垃圾回收");
// 通知JVM的gc进行垃圾回收
System.gc();
}
Вывод программы:
ref1 value = value1, ref2 value = value2, ref3 value = value3
开始通知JVM的gc进行垃圾回收
引用对象被回收, ref = java.lang.ref.WeakReference@48c6cd96, value=null
引用对象被回收, ref = java.lang.ref.WeakReference@46013afe, value=null
引用对象被回收, ref = java.lang.ref.WeakReference@423ea6e6, value=null
5.4 Фантомные ссылки
Фантомные ссылки также называются призрачными ссылками или фантомными ссылками.В отличие от мягких и слабых ссылок, виртуальные ссылки не используются для доступа к объекту, указанному ссылочным объектом.Наоборот,События повторного использования объекта можно получить, непрерывно опрашивая очередь ссылок, связанную с виртуальным эталонным объектом.. Наличие у объекта виртуальной ссылки никак не повлияет на время его создания, а экземпляр объекта нельзя получить через виртуальную ссылку. Хотя это может показаться бессмысленным, на самом деле его можно использовать для переработки объектов.Очистка и освобождение ресурсов, который является более гибким, чем finalize, мы можем сделать более безопасное и надежное восстановление ресурсов, связанных с объектами, на основе виртуальных ссылок.
- проблема с финализацией
- Спецификация языка Java не гарантирует, что метод завершения будет выполнен вовремя, и он не гарантирует, что они будут выполнены. Если доступная память не исчерпана, сборщик мусора не будет запущен, а метод завершения доработки не будет выполнен.
- проблемы с производительностью JVM обычно завершает выполнение finalize в отдельном потоке с низким приоритетом.
- проблема регенерации объекта В методе finalize объект, подлежащий повторному использованию, может быть назначен ссылке на объект, доступной для GC Roots, чтобы достичь цели регенерации объекта.
Для ненадежных методов финализации можно использовать виртуальные ссылки для их реализации. После JDK1.2 класс PhantomReference предоставляется для реализации виртуальных ссылок.
Ниже приведен простой пример использования. Событие утилизации объекта можно получить, обратившись к очереди ссылок:
/**
* 简单使用虚引用demo
* 虚引用在实现一个对象被回收之前必须做清理操作是很有用的,比finalize()方法更灵活
*/
private static void simpleUsePhantomRefDemo() throws InterruptedException {
Object obj = new Object();
ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, refQueue);
// null
System.out.println(phantomRef.get());
// null
System.out.println(refQueue.poll());
obj = null;
// 通知JVM的gc进行垃圾回收
System.gc();
// null, 调用phantomRef.get()不管在什么情况下会一直返回null
System.out.println(phantomRef.get());
// 当GC发现了虚引用,GC会将phantomRef插入进我们之前创建时传入的refQueue队列
// 注意,此时phantomRef对象,并没有被GC回收,在我们显式地调用refQueue.poll返回phantomRef之后
// 当GC第二次发现虚引用,而此时JVM将phantomRef插入到refQueue会插入失败,此时GC才会对phantomRef对象进行回收
Thread.sleep(200);
Reference<?> pollObj = refQueue.poll();
// java.lang.ref.PhantomReference@1540e19d
System.out.println(pollObj);
if (null != pollObj) {
// 进行资源回收的操作
}
}
Чаще всего такие сценарии, как создание пула соединений JDBC и снятие блокировки, могут быть реализованы на основе виртуальных ссылок. Взяв в качестве примера пул соединений, вызывающая сторона должна освободить соединение обратно в пул после использования соединения в обычных условиях. Обертывая объекты подключения на основе виртуальных ссылок и связывая очереди ссылок, вы можете опросить очереди ссылок, чтобы проверить, какие объекты подключения были восстановлены сборщиком мусора, и освободить соответствующие ресурсы подключения.Конкретная реализация была загружена на склад caison-blog-demo на github..
6 Резюме
Сравните различия между несколькими эталонными объектами:
тип ссылки | время сбора ГХ | общего пользования | время выживания |
---|---|---|---|
сильная цитата | никогда | общее состояние объекта | Когда JVM перестает работать |
мягкая ссылка | Когда памяти мало | кеш объектов | Завершить, когда не хватает памяти |
слабая ссылка | время GC | кеш объектов | Завершить после GC |
Виртуальная ссылка используется вместе с очередью ссылок для получения событий повторного использования объектов путем непрерывного опроса очереди ссылок.
Хотя ссылочные объекты — очень полезный инструмент для управления потреблением памяти, иногда их недостаточно или они перегружены. Например, используйте карту для кэширования данных, считанных из базы данных. Хотя слабые ссылки могут использоваться в качестве кешей, окончательная программа должна использовать определенный объем памяти. Не имеет значения, насколько мощным является механизм восстановления после ошибок, если ему не может быть предоставлено достаточно ресурсов, чтобы он мог что-либо сделать.
При столкновении с ошибкой OutOfMemoryError первая реакция — выяснить, почему это произошло, может быть, в программе ошибка, может быть, доступной памяти установлено слишком мало.
В процессе разработки следует сформулировать конкретный объем памяти программы, но обращать внимание на то, сколько памяти используется при фактическом использовании. При фактической рабочей нагрузке большинства приложений использование памяти программой достигнет стабильного состояния, которое можно использовать в качестве эталона для установки разумного размера кучи. Если использование памяти программой со временем увеличивается, это, скорее всего, связано с тем, что она все еще содержит сильные ссылки на объекты, когда они больше не используются. Здесь могут помочь ссылочные объекты, но, скорее всего, это будет исправлено как ошибка.
Весь исходный код, задействованный в статье, выложен на github по адресу:GitHub.com/Это ты/Это...
Более интересно, добро пожаловать на официальный аккаунт автора [архитектура распределенной системы]
Ссылаться на
«Глубокое понимание виртуальной машины Java — расширенные функции и рекомендации JVM (2-е издание)»
Базовая технология Java 36 лекций
Глубокое погружение в виртуальную машину Java
Как эффективно избежать OOM в Java: эффективно используйте мягкие и слабые ссылки