无法访问的对象不是从堆中收集的垃圾

我在JVM堆(Java 1.7)中遇到了无法访问的对象。 正如你从图片中看到的那样(图片上的所有类都无法访问),我们有超过74%的对象没有参考,所以它应该被收集起来。 这个状态在我们的tomcat 7服务器上运行3周之后,只运行探测监控应用程序,tomcat管理器和我们的webapp,这可能是问题的根源。

我们的应用程序基于JSF 1.2,在客户端上保存状态,如下图所示 – 主要使用ViewSaveState的char数组。 当我从jVisualVM手动运行GC时,它会删除所有无法访问的对象,并且在堆达到其限制的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

OutOfMemoryError的STACKTRACES

我认为原因隐藏在其他地方,堆栈跟踪来自app的不同部分。 可能存在一些泄漏,但堆栈跟踪仅报告最后一个组件,该组件在没有任何内存的情况下及时声明了一些内存。

  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.(String.java:443) at com.ibm.db2.jcc.aaa(a.java:632) at com.ibm.db2.jcc.aaa(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) 

一种可能性是你在finalize()方法中使用病态行为重载JVM。 如果你有覆盖Object.finalize()的类,那么JVM必须做大量的工作才能真正清理它们(反过来,清理它们所有引用的对象)。 如果你创建这样的对象比垃圾收集器处理它们更快,你很快就会遇到麻烦。

本文详细介绍了病理终结器示例 ,但总结如下:

  1. 具有finalize()方法的对象超出范围,并且(在概念上)符合GC的条件。
  2. 与可以简单释放的普通对象不同,JVM通过Finalizer保存对对象的特殊引用,从而防止这些对象被轻易收集。 即使短暂的对象仍然存在于初始GC中,并且如果与finalize()相关联,则会移动到堆的更长寿命的部分。
  3. 现在这些对象将由专用的Finalizer线程处理,该线程将.finalize()调用每个对象上的.finalize() 。 虽然它们无法访问,但尚未轮到它们的对象仍保留在堆上。
  4. 一旦Finalizer线程处理了该对象,就会删除最后一个引用,并且该对象最终可以实际上是GC。 由于该对象在一个或多个收集轮中存活,因此GC可能需要一些时间来绕过它。
  5. 依次引用的可终结对象的任何对象现在才有资格进行收集。

如果.finalize()方法需要特别长的时间,或者如果您正在创建大量此类对象,则Finalizer线程无法满足需求,对象将继续排队,最终填满整个堆。

还有其他可能的解释,但是过度使用finalize()是一个可能的原因。 有效的Java Item 7强烈反对终结者 :

终结器是不可预测的,通常是危险的,并且通常是不必要的。 它们的使用会导致不稳定的行为,性能不佳和可移植性问题….

在极少数情况下,为类提供终结器可以任意延迟其实例的回收。

这个问题可能与此问题有关

Java堆被无法访问的对象所淹没

我发现它在AIX 6.1上的IBM JVM 5 runninf中遇到了同样的问题

在两个fullgcs之间一致增长的保留堆的唯一数量是无法访问的对象,如果我没有错,则标记为ROOT作为Eclipse MAT中的支配者。

为了获得这些转储,我将JVM配置为使用Xdump选项在fullgc之后创建一个sysdump(我认为仅适用于IBM JVM)

希望这会有所帮助,如果有人知道GC详细日志是否留下了无法清除的无法访问的对象的痕迹,请告诉我们!

卡洛斯

您面临的行为有几个方面。 首先,在任何给定时间内堆内都有无法访问的对象是完全正常的。 垃圾收集将在下次运行期间清除无法访问的对象,并从中清除堆。 因此,在堆转储中看到无法访问的数据结构不是邪恶的,也不是exception的。

但是当面对java.lang.OutOfMemoryError:Java堆空间错误时,应该清除这些无法访问的引用。 您可以通过向启动参数添加-XX:+ HeapDumpOnOutOfMemoryError来检查这一点,在生成的下一个OutOfMemoryError上触发堆转储。 当您现在对此转储进行爬网时,您不应该看到无法访问的对象,至少没有任何大小的对象。

在理解了这一点后,潜在的问题仍然不明确,其中一个可能的原因肯定是堆泄漏。 为此,您可以尝试使用OutOfMemoryError在JVM崩溃期间获得相同的内存转储,或者您可以使您的生活更轻松,并附加Java内存泄漏检测器以查找源代码中泄漏的确切位置。