Одна из наших программ иногда выдает ошибку OutOfMemory на компьютере одного пользователя, но, конечно, не тогда, когда я ее тестирую. Я просто запустил его с помощью JProfiler (с 10-дневной оценочной лицензией, потому что я никогда не использовал его раньше) и, фильтруя по префиксу кода, самый большой кусок как по общему размеру, так и по количеству экземпляров составляет 8000+ экземпляров определенного простого класса .
Я нажал кнопку «Сбор мусора» в JProfiler, и большинство экземпляров других наших классов исчезли, но не эти. Я снова запустил тест, все еще в том же экземпляре, и он создал еще 4000+ экземпляров класса, но когда я щелкнул «Сбор мусора», они исчезли, оставив 8000+ исходных.
Эти экземпляры застревают в различных коллекциях на разных этапах. Я предполагаю, что тот факт, что они не собирают мусор, должен означать, что что-то удерживает ссылку на одну из коллекций, чтобы удерживать ссылку на объекты.
Любые предложения, как я могу выяснить, что держится за ссылку? Я ищу предложения о том, что искать в коде, а также способы узнать это в JProfiler, если они есть.




Следите за статическими контейнерами. Любые объекты в статическом контейнере будут оставаться, пока класс загружен.
Обновлено: удалено неправильное замечание на WeakReference.
Если вы получаете ошибки OOM на языке со сборщиком мусора, это обычно означает, что сборщик не учитывает некоторую память. Может быть, ваши объекты содержат ресурсы, отличные от Java? если да, то у них должен быть какой-то метод «закрытия», чтобы гарантировать, что этот ресурс освобожден, даже если объект Java не будет собран достаточно скоро.
Один очевидный кандидат - объекты с финализаторами. Они могут задерживаться, пока вызывается их метод finalize. Их нужно собрать, затем завершить (обычно с помощью всего одного потока финализатора), а затем снова собрать.
Также имейте в виду, что вы можете получить OOME, потому что gc не смог собрать достаточно памяти, несмотря на то, что на самом деле ее достаточно для создания запроса объекта. В противном случае производительность врезалась бы в землю.
Нет серебряной пули, вы должны использовать профилировщик, чтобы идентифицировать коллекции, содержащие эти ненужные объекты, и найти место в коде, где они должны были быть удалены. Как сказал JesperE, в первую очередь нужно смотреть на статические коллекции.
Я бы посмотрел на Коллекции (особенно статические) в ваших классах (HashMaps - хорошее место для начала). Возьмем, к примеру, этот код:
Map<String, Object> map = new HashMap<String, Object>(); // 1 Object
String name = "test"; // 2 Objects
Object o = new Object(); // 3 Objects
map.put(name, o); // 3 Objects, 2 of which have 2 references to them
o = null; // The objects are still being
name = null; // referenced by the HashMap and won't be GC'd
System.gc(); // Nothing is deleted.
Object test = map.get("test"); // Returns o
test = null;
map.remove("test"); // Now we're down to just the HashMap in memory
// o, name and test can all be GC'd
Пока HashMap или какая-либо другая коллекция имеет ссылку на этот объект, сборщик мусора не будет.
Если бы после первого System.gc () не было дальнейших ссылок на «карту», разве это не было бы сборщиком мусора?
Да, если на HashMap больше ничего не ссылается, тогда он будет иметь право на сборку мусора.
Возможно, это изменилось, но разве JVM не игнорирует вызов system.gc и не делает ли это всякий раз, когда ей хочется?
Согласно API, «вызов метода gc предполагает, что виртуальная машина Java затрачивает усилия на переработку неиспользуемых объектов, чтобы сделать память, которую они в настоящее время занимают, доступной для быстрого повторного использования». Итак, вы как бы вежливо спрашиваете и намекаете, что хотите, чтобы это было сделано.
Я только что прочитал об этом статью, но извините, я не могу вспомнить где. Думаю, это могло быть в книге «Эффективная Java». Если найду ссылку, обновлю свой ответ.
В нем изложены два важных урока:
1) Final методы сообщают gc, что делать, когда он отбирает объект, но он не просит его об этом, и нет способа потребовать этого.
2) Современный эквивалент «утечки памяти» в неуправляемых средах памяти - это забытые ссылки. Если вы не установите для всех ссылок на объект значение нулевой, когда закончите с ним, объект будет отбракован как никогда. Это наиболее важно при реализации вашего собственного типа Collection или вашей собственной оболочки, которая управляет коллекцией. Если у вас есть пул, стек или очередь, и вы не устанавливаете для корзины значение нулевой, когда вы «удаляете» объект из коллекции, корзина, в которой находился объект, будет поддерживать этот объект в живых до тех пор, пока для этого ведра не будет установлено значение относятся к другому объекту.
отказ от ответственности: я знаю, что об этом упоминалось в других ответах, но я пытаюсь предложить более подробную информацию.
Я бы подумал, что вам не нужно обнулять ссылку на объект, если он выходит за рамки.
В п. 24 Блоха «Эффективная Java». Правило 6: Удалите устаревшие ссылки на объекты, есть пример плохо реализованного Stack, где его элементы кэшируются в элементах Object [], но они никогда не обнуляются из массива elements при вызове pop ().
Попробуйте Eclipse Memory Analyzer. Он покажет вам для каждого объекта, как он подключен к корню GC - объекту, который не собирается сборщиком мусора, потому что он удерживается JVM.
См. http://dev.eclipse.org/blogs/memoryanalyzer/2008/05/27/automated-heap-dump-analysis-finding-memory-leaks-with-one-click/ для получения дополнительной информации о том, как работает Eclipse MAT.
Сбросьте кучу и осмотрите ее.
Я уверен, что есть несколько способов сделать это, но вот простой. Это описание предназначено для MS Windows, но аналогичные шаги можно предпринять и в других операционных системах.
Вот пример:
C:\dump>jmap -dump:format=b,file=heap.bin 3552
C:\dump>jhat heap.bin
Reading from heap.bin...
Dump file created Tue Sep 30 19:46:23 BST 2008
Snapshot read, resolving...
Resolving 35484 objects...
Chasing references, expect 7 dots.......
Eliminating duplicate references.......
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
Чтобы интерпретировать это, полезно понять, что номенклатура типов массива использует Java - например, знать, что класс [Ljava.lang.Object; действительно означает объект типа Объект[].
Будьте осторожны при создании дампа с помощью jmap: вам может потребоваться запустить jmap от имени пользователя jvm, к которому вы подключаетесь. См. это.
также будьте осторожны, вам нужно предоставить дополнительную память кучи для jhat, например "-J-mx6G", для успешного запуска сервера для дампа кучи 4 ГБ
Я использовал профилировщик Java Yourkit (http://www.yourkit.com) для оптимизации производительности на java 1.5. В нем есть раздел о том, как работать с утечками памяти. Я считаю это полезным.
http://www.yourkit.com/docs/75/help/performance_problems/memory_leaks/index.jsp
Вы можете получить 15-дневную оценку: http://www.yourkit.com/download/yjp-7.5.7.exe
BR,
~ А
Коллекции уже упоминалось. Еще одно труднодоступное место - это использование нескольких загрузчиков классов, так как старый загрузчик классов может быть не в состоянии собирать мусор, пока не исчезнут все ссылки.
Также проверьте статику - это мерзко. Фреймворки ведения журнала могут держать вещи открытыми, что может содержать ссылки в пользовательских приложениях.
Вы решили проблему?
Нет, меня уволили. Так что это больше не моя проблема.
Некоторые предложения:
Если вы ищете бесплатный профилировщик, я предлагаю вам взглянуть на jiprof.sourceforge.net. Может быть, немного старомодно, без модного графического интерфейса и т. д., Но работает в большинстве случаев.