2008-09-30 1 views
14

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

Я нажал кнопку «Сбор мусора» на JProfiler, и большинство экземпляров других классов ушли, но не эти конкретные. Я снова проверил тест, все еще в том же экземпляре, и создал еще 4000 экземпляров класса, но когда я нажал «Сбор мусора», они ушли, оставив исходные 8000+.

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

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

+0

Если вы ищете бесплатный профайлер, я предлагаю вам ознакомиться с http://jiprof.sourceforge.net/. Может быть, немного старомодный, не причудливый gui и так далее, но работающий в большинстве случаев. – dhiller 2008-10-01 10:53:41

ответ

18

Сбросьте кучу и осмотрите ее.

Я уверен, что существует несколько способов сделать это, но вот простой. Это описание относится к MS Windows, но аналогичные шаги могут быть предприняты и для других операционных систем.

  1. Установите JDK, если у вас его еще нет. It comes with a bunch of neat tools.
  2. Запуск приложения.
  3. Откройте диспетчер задач и найдите идентификатор процесса (PID) для java.exe (или любого исполняемого файла, который вы используете). Если PID не отображаются по умолчанию, используйте «Просмотр»> «Выбрать столбцы ...», чтобы добавить их.
  4. Сбросьте кучу, используя jmap.
  5. Запустите сервер jhat на файл, который вы сгенерировали и откройте браузер http://localhost:7000 (порт по умолчанию 7000). Теперь вы можете просмотреть интересующий вас тип и информацию, такую ​​как количество экземпляров, ссылки на них и т. Д.

Вот пример:

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. 

Для интерпретации этого, полезно понять некоторые из array type nomenclature Java использует - как знать, что класс [Ljava.lang.Object; действительно означает объект типа Объект [].

2

Следите за статическими контейнерами. Любые объекты в статическом контейнере остаются до тех пор, пока класс загружается.

Редактировать: удалено неверное примечание по WeakReference.

0

Если вы получаете ошибки OOM на собранном мусором языке, это обычно означает, что сборщик не учитывает какую-либо память. Возможно, ваши объекты хранят ресурсы, отличные от java? если это так, тогда у них должен быть какой-то «закрытый» метод, чтобы убедиться, что ресурс выпущен, даже если объект Java не собирается достаточно скоро.

2

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

Также имейте в виду, что вы можете получить OOME, потому что gc не смог собрать достаточную память, несмотря на то, что на самом деле этого достаточно для создания объекта запроса. В противном случае производительность будет измельчаться в землю.

3

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

5

Я бы посмотрел на коллекции (особенно статические) в ваших классах (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 или какая-либо другая коллекции есть ссылка на этот объект не будет собран сборщиком мусора.

+0

Если не было никакой дополнительной ссылки на «карту» после первой System.gc(), не собирался ли это сбор мусора? – 2008-09-30 19:12:31

+0

Да, если ничего не ссылается на HashMap, тогда он будет иметь право быть GC'd. – 18Rabbit 2008-09-30 19:17:19

+0

Возможно, это изменилось, но JVM не игнорирует вызов system.gc и делает это каждый раз, когда это кажется? – tloach 2008-09-30 19:19:17

1

Я только что прочитал статью об этом, но мне жаль, что я не могу вспомнить, где. Я думаю, что это могло быть в книге «Эффективная Ява». Если я найду ссылку, я обновлю свой ответ.

два важных урока Определились являются:

1) Окончательные методы сообщают дс, что делать, когда он вырезает объект, но он не просит его сделать это, и есть способ требовать что он делает.

2) Современный эквивалент «утечки памяти» в неуправляемых средах памяти - это забытые ссылки. Если вы не установили все ссылки на объект на , то нуль, когда вы закончите с этим, объект будет никогда не будет. Это наиболее важно при реализации собственного вида коллекции или собственной оболочки, управляющей коллекцией. Если у вас есть пул или стек или очередь, и вы не устанавливаете ведро в null, когда вы «удаляете» объект из коллекции, ведро, в котором находился объект, будет поддерживать этот объект до тех пор, пока этот ковш не будет чтобы ссылаться на другой объект.

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

9

Попробуйте анализатор памяти Eclipse. Он покажет вам для каждого объекта, как он подключен к корню GC - объект, который не является сборкой мусора, потому что он удерживается JVM.

Для получения дополнительной информации о работе Mcl Eclipse см. http://dev.eclipse.org/blogs/memoryanalyzer/2008/05/27/automated-heap-dump-analysis-finding-memory-leaks-with-one-click/.

1

Я использовал профилировщик Mykit Java (http://www.yourkit.com) для оптимизации производительности на Java 1.5. В нем есть раздел о том, как работать с утечками памяти. Я считаю это полезным.

http://www.yourkit.com/docs/75/help/performance_problems/memory_leaks/index.jsp

Вы можете получить 15 дней Eval: http://www.yourkit.com/download/yjp-7.5.7.exe

BR,
~ A

1

Коллекции уже упоминалось.Еще одно труднодоступное местоположение - если вы используете несколько ClassLoaders, поскольку старый загрузчик классов может быть неспособен собирать мусор до тех пор, пока все ссылки не исчезнут.

Также проверьте статику - это неприятно. Фреймворки регистрации могут держать открытыми вещи, которые могут содержать ссылки в пользовательских приложениях.

У вас возникли проблемы?

1

Некоторые предложения:

  • Неограниченные карты, используемые в качестве тайников, особенно при статической
  • ThreadLocals в серверных приложениях, поскольку потоки обычно не умирают, так что ThreadLocal не освобожденные
  • интернирование строки (Strings.intern()), что приводит к куче строк в PermSpace