Учитывая совокупность экземпляров классов, которые ссылаются друг на друга сложным, циклическим образом: возможно ли, что сборщик мусора не сможет освободить эти объекты?
Я смутно припоминаю, что это было проблемой в JVM в прошлом, но я мысль это было решено много лет назад. Тем не менее, некоторое исследование в jhat показало, что причиной утечки памяти, с которой я сейчас столкнулся, является циклическая ссылка.
Примечание. Мне всегда казалось, что JVM способна разрешать циклические ссылки и освобождать такие «островки мусора» из памяти. Однако я задаю этот вопрос, чтобы посмотреть, не нашел ли кто-нибудь исключения.




Сборщик мусора знает, где находятся корневые объекты: статика, локальные переменные в стеке и т. д., И если объекты недоступны из корня, они будут восстановлены. Если они достижимы, то им нужно остаться.
Сборщик мусора - очень сложное программное обеспечение - оно было протестировано в огромном наборе тестов JCK. Это НЕ идеально, НО есть очень хороший шанс, что до тех пор, пока компилятор java (javac) будет компилировать все ваши классы, а JVM создаст его экземпляры, все будет хорошо.
Опять же, если вы удерживаете ссылки на корень этого графа объектов, память НЕ будет освобождена, НО если вы знаете, что делаете, все должно быть в порядке.
Нет, по крайней мере, используя официальную JVM Sun, сборщик мусора сможет обнаруживать эти циклы и освобождать память, как только больше не будет никаких внешних ссылок.
Просто чтобы усилить то, что уже было сказано:
Приложение, над которым я работал шесть лет, недавно изменилось с Java 1.4 на Java 1.6, и мы обнаружили, что нам приходилось добавлять статические ссылки на вещи, о которых мы даже не подозревали, что раньше можно было собирать мусор. Раньше нам не требовалась статическая ссылка, потому что раньше сборщик мусора был отстойным, а сейчас он стал намного лучше.
Как вы узнали, собираются ли объекты сборщиком мусора, если вы, должно быть, отбросили все ссылки на них? Были ли это слабые ссылки, или у вас была ссылка на них через JNI, или это был какой-то живой объект, который можно собрать, даже если он все еще активен?
Проблема заключалась в том, что когда мы обратились к нему, его уже не было. Я забыл точные детали, потому что это было несколько месяцев назад, но я думаю, что мы вызывали это через метод RMI.
Оказывается, утечка памяти может быть прослежена до класса, экземпляры которого хранятся в коллекции, а сам класс имеет ссылку (типа Object) на рассматриваемые объекты класса - нет, я не писал код для этого :-)
Только в очень наивной реализации возникнут проблемы с циклическими ссылками. В Википедии есть хороший статья по различным алгоритмам сборки мусора. Если вы действительно хотите узнать больше, попробуйте (Amazon) Сборка мусора: алгоритмы автоматического управления динамической памятью. У Java был хороший сборщик мусора с версии 1.2 и исключительно хороший сборщик мусора в версиях 1.5 и Java 6.
Самая сложная часть улучшения GC - это сокращение пауз и накладных расходов, а не элементарных вещей, таких как циклическая ссылка.
Сборщики мусора с подсчетом ссылок печально известны этой проблемой. Примечательно, что Suns JVM не использует сборщик мусора с подсчетом ссылок.
Если объект не может быть достигнут из корня кучи (обычно, как минимум, через загрузчики классов, если ничто иное), то объекты будут уничтожены, поскольку они не копируются во время типичного Java GC в новую кучу.
Подсчет ссылок - очень хороший пример наивной реализации.
На самом деле, это своего рода умная реализация, это также довольно простая реализация, и, хотя у нее есть свои ограничения, это жизнеспособный алгоритм, особенно в средах с ограниченным объемом памяти.
Я читал о разработке MacOs, и, очевидно, ObjectiveC поддерживает как сборку мусора на основе подсчета ссылок, так и более сложную схему, аналогичную схеме Java.
В спецификации Java сказано, что сборщик мусора может собирать мусор для вашего объекта. ТОЛЬКО если это недоступен из любого потока.
Доступный означает, что существует ссылка или цепочка ссылок, ведущая от A к B, и может пройти через C, D, ... Z независимо от того, что ему нужно.
JVM не собирает вещи для меня с 2000 года, но ваш опыт может отличаться.
Совет: сериализация Java кэширует объекты, чтобы сделать передачу объектной сетки более эффективной. Если у вас много больших временных объектов и вся ваша память перегружена, сбросьте сериализатор, чтобы очистить его кеш.
Если я правильно помню, то по спецификациям есть только гарантии того, что JVM не может собрать (все, что доступно), а не то, что он будет собирать.
Если вы не работаете с JVM в реальном времени, большинство современных сборщиков мусора должны уметь обрабатывать сложные ссылочные структуры и определять «подграфы», которые можно безопасно удалить. Эффективность, задержка и вероятность этого со временем улучшаются, поскольку все больше исследовательских идей внедряются в стандартные (а не исследовательские) виртуальные машины.
И чтобы подчеркнуть, что JVM, которая предполагает бесконечную кучу и никогда не собирает мусор, полностью совместима (хотя и неэффективна ;-)
Райан, судя по твоему комментарию к Циркулярные ссылки в Java, ты попал в ловушку ссылки на объекты из класса, который, вероятно, был загружен загрузчиком классов начальной загрузки / системы. На каждый класс ссылается загрузчик классов, который загрузил класс, и, таким образом, он может быть удален сборщиком мусора только в том случае, если загрузчик классов больше недоступен. Загвоздка в том, что загрузчик классов / системный загрузчик никогда не собирает мусор, поэтому объекты, доступные из классов, загруженных системным загрузчиком классов, тоже не могут быть собраны.
Причина такого поведения объясняется в JLS. Например, третье издание 12.7 http://java.sun.com/docs/books/jls/third_edition/html/execution.html#12.7.
На самом деле, мы думали, что мы это выяснили, но, к сожалению, теперь похоже, что у нас этого нет. Но да, это одна из возможностей, которую мы должны изучить.
Циклическая ссылка происходит, когда один объект ссылается на другой, а этот другой ссылается на первый объект. Например:
class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
public class Main {
public static void main(String[] args) {
A one = new A();
B two = new B();
// Make the objects refer to each other (creates a circular reference)
one.setB(two);
two.setA(one);
// Throw away the references from the main method; the two objects are
// still referring to each other
one = null;
two = null;
}
}
Сборщик мусора Java достаточно умен, чтобы очищать объекты, если есть циклические ссылки, но больше нет живых потоков, которые имеют какие-либо ссылки на объекты. Таким образом, наличие такой круговой ссылки не приводит к утечке памяти.
Если до него нельзя было добраться, как вы узнали, что он исчез? Интересно, есть ли какая-то ссылка, о которой вы не думаете, которая может заставить систему ее сохранить. Нить или слушатель.