Недостижимыми объектами не являются мусор, собранный из кучи

Я борюсь с недостижимыми объектами в своей куче JVM (Java 1.7). Как вы можете видеть на картинке (все классы на картинке недостижимы), у нас есть более 74% объектов без ссылки, поэтому их нужно собрать в сумку. Это состояние становится после 3-х недельного обновления на нашем сервере tomcat 7, где запускается только приложение мониторинга зонда, менеджер tomcat и наш webapp, который, вероятно, является источником проблемы.

Наше приложение основано на JSF 1.2 с сохранением состояния на клиенте, что в основном представлено на рисунке ниже - char массивы с ViewSaveState. Когда я вручную запускаю GC из jVisualVM, он удаляет все недостижимые объекты, и все в порядке до 3 недель, когда куча достигает своего предела.

Как возможно, что некоторые объекты не очищены?

Наши параметры JVM

-Dcom.sun.management.jmxremote=true
-Dcom.sun.management.jmxremote.port=29001
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=
-Dorg.apache.el.parser.SKIP_IDENTIFIER_CHECK=true
-Xms320m
-Xmx2500m
-XX:MaxPermSize=500m
-XX:PermSize=96m
-verbose:gc
-Xloggc:/var/log/gc.log
-XX:+PrintGCTimeStamps
-XX:+PrintGCDetails
-Xdebug -Xrunjdwp:transport=dt_socket,address=1044,server=y,suspend=n
-XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC

http://puu.sh/b7mjB/3d23de7d41.png

STACKTRACES для OutOfMemoryError

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

    java.lang.OutOfMemoryError: Java heap space
            at java.util.LinkedHashMap.createEntry(LinkedHashMap.java:442)
            at java.util.HashMap.addEntry(HashMap.java:888)
            at java.util.LinkedHashMap.addEntry(LinkedHashMap.java:427)
            at java.util.HashMap.put(HashMap.java:509)
            at sun.util.resources.OpenListResourceBundle.loadLookup(OpenListResourceBundle.java:134)
            at sun.util.resources.OpenListResourceBundle.loadLookupTablesIfNecessary(OpenListResourceBundle.java:113)
            at sun.util.resources.OpenListResourceBundle.handleGetObject(OpenListResourceBundle.java:74)
            at sun.util.resources.TimeZoneNamesBundle.handleGetObject(TimeZoneNamesBundle.java:75)
            at java.util.ResourceBundle.getObject(ResourceBundle.java:389)
            at java.util.ResourceBundle.getObject(ResourceBundle.java:392)
------------------
Exception in thread "Timer-22" Exception in thread "com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#2" java.lang.OutOfMemoryError: Java heap space
Exception in thread "com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#1" java.lang.OutOfMemoryError: Java heap space
------------------
Caused by: java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOf(Arrays.java:2219)
        at java.util.ArrayList.grow(ArrayList.java:242)
        at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)
        at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)
        at java.util.ArrayList.add(ArrayList.java:440)
        at org.hibernate.loader.Loader.instanceNotYetLoaded(Loader.java:1468)
        at org.hibernate.loader.Loader.getRow(Loader.java:1355)
        at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:611)
        at org.hibernate.loader.Loader.doQuery(Loader.java:829)
        at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274)
         at org.ajax4jsf.component.AjaxActionComponent.broadcast(AjaxActionComponent.java:55)
        at org.ajax4jsf.component.AjaxViewRoot.processEvents(AjaxViewRoot.java:329)
        at org.ajax4jsf.component.AjaxViewRoot.broadcastEventsForPhase(AjaxViewRoot.java:304)
        at org.ajax4jsf.component.AjaxViewRoot.processPhase(AjaxViewRoot.java:261)
        at org.ajax4jsf.component.AjaxViewRoot.processApplication(AjaxViewRoot.java:474)
        at org.apache.myfaces.lifecycle.InvokeApplicationExecutor.execute(InvokeApplicationExecutor.java:32)
        at org.apache.myfaces.lifecycle.LifecycleImpl.executePhase(LifecycleImpl.java:103)
        at org.apache.myfaces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:76)
        at javax.faces.webapp.FacesServlet.service(FacesServlet.java:183)
        ... 74 more
--------------
Caused by: java.lang.OutOfMemoryError: Java heap space
        at java.nio.ByteBuffer.wrap(ByteBuffer.java:350)
        at java.lang.StringCoding$StringDecoder.decode(StringCoding.java:137)
        at java.lang.StringCoding.decode(StringCoding.java:173)
        at java.lang.String.<init>(String.java:443)
        at com.ibm.db2.jcc.a.a.a(a.java:632)
        at com.ibm.db2.jcc.a.a.a(a.java:355)
        at com.ibm.db2.jcc.am.fc.e(fc.java:682)
        at com.ibm.db2.jcc.am.fc.k(fc.java:1481)
        at com.ibm.db2.jcc.am.ResultSet.getTimestampX(ResultSet.java:1075)
        at com.ibm.db2.jcc.am.ResultSet.getTimestamp(ResultSet.java:1034)

Ответ 1

Одна из возможностей заключается в том, что вы перегружаете JVM патологическим поведением в методах finalize(). Если у вас есть классы, которые переопределяют Object.finalize(), JVM должен выполнять удивительную работу, чтобы фактически очистить их (и, в свою очередь, очистить все объекты, на которые они ссылаются). Если вы создадите такие объекты быстрее, чем сборщик мусора может их обработать, вы быстро столкнетесь с проблемами.

В этой статье подробно описывается патологический пример финализатора, но вкратце:

  • Объект с методом finalize() выходит за рамки и (концептуально) становится приемлемым для GC.
  • В отличие от обычного объекта, который может быть просто освобожден, JVM имеет специальную ссылку на объект через Finalizer, предотвращая эти объекты от тривиального сбора. Даже короткоживущие объекты выживают в исходном GC и переходят в более длинные участки кучи, если они связаны с finalize().
  • Теперь эти объекты будут обработаны выделенным потоком Finalizer, который по очереди будет вызывать .finalize() для каждого объекта. Объекты, которые еще не имели своей очереди, остаются на куче, хотя они недоступны.
  • Как только объект обрабатывается потоком Finalizer, эта последняя ссылка удаляется, и объект может, наконец, фактически быть GC'ed. Так как объект пережил один или несколько раундов коллекций, может потребоваться некоторое время для того, чтобы GC приблизился к нему.
  • Любые объекты, на которые ссылается окончательный объект, теперь доступны только для коллекции.

Если ваши методы .finalize() занимают особенно много времени, или если вы создаете большое количество таких объектов, поток Finalizer не может справиться с требованием, и объекты будут продолжать стоять в очереди, в конечном итоге заполняя ваши целая куча.

Существуют и другие возможные объяснения, но чрезмерное использование finalize() является вероятной причиной. Эффективный Java-элемент 7 сильно запрещает финализаторы:

Финализаторы непредсказуемы, часто опасны и обычно не нужны. Их использование может привести к неустойчивому поведению, плохой производительности и проблемам переносимости....

Предоставление финализатора для класса может в редких условиях произвольно задерживать восстановление его экземпляров.

Ответ 2

Этот вопрос может быть связан с этим

Куча Java перегружена недоступными объектами

Я нашел это, поскольку столкнулся с той же проблемой в IBM JVM 5 runninf на AIX 6.1

Единственное количество сохраненной кучи, которая постоянно растет между двумя полными командами, - это недостижимые объекты, отмеченные, если я не ошибаюсь, с ROOT как доминирующим элементом в Eclipse MAT.

Чтобы получить эти дампы, я настраиваю JVM для создания sysdump после fullgc, используя параметр Xdump (только для IBM JVM, я думаю)

Надеюсь, что это поможет, и если кто-нибудь знает, оставляет ли длинный журнал GC недостаток недостижимых объектов, которые невозможно очистить, сообщите мне!

Карлос

Ответ 3

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

Но когда вы сталкиваетесь с java.lang.OutOfMemoryError: Ошибка кучи Java-памяти, эти недостижимые ссылки должны быть очищены. Вы можете проверить это, добавив -XX: + HeapDumpOnOutOfMemoryError к вашим параметрам запуска, вызывая сброс кучи при следующем выпуске OutOfMemoryError. Когда вы теперь сканируете этот дамп, вы не должны видеть недоступные объекты, по крайней мере, не имеющие значительного размера.

Понимая это, основная проблема остается неясной, одна из возможных причин может быть причиной утечки кучи. Для этого вы можете попытаться использовать тот же самый дамп памяти, который был получен во время сбоя JVM с помощью OutOfMemoryError, или вы можете сделать свою жизнь проще и приложить детектор утечки памяти Java, чтобы найти точное местоположение утечки в исходном коде.