【问题标题】:Unreachable objects are not garbage collected from heap无法访问的对象不是从堆中收集的垃圾
【发布时间】:2014-10-18 19:46:07
【问题描述】:

我正在为我的 JVM 堆 (Java 1.7) 中无法访问的对象而苦苦挣扎。从图中可以看出(图上所有的类都是不可达的),我们有超过 74% 的对象没有引用,所以应该垃圾收集。在我们的 tomcat 7 服务器上运行 3 周后,此状态变为仅运行 Probe 监控应用程序、tomcat 管理器和我们的 web 应用程序,这可能是问题的根源。

我们的应用程序基于 JSF 1.2,在客户端保存状态,如下图所示 - 主要是带有 ViewSaveState 的字符数组。 当我从 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

OutOfMemoryError 的堆栈跟踪

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

    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)

【问题讨论】:

  • “堆达到极限”是什么意思?你得到 OutOfMemoryError 了吗?
  • 您是否在 gc 日志中看到诸如“升级失败”或“并发模式失败”之类的错误消息?顺便说一句,您的服务器上的负载如何?一般来说,大约 2.5G 的堆空间对于服务器来说是非常少的。
  • 我们有一个很小的应用程序,只有在有 10 个并发用户的情况下,第一次运行时才从堆中占用 400MB,而且这种情况并不经常发生。所以应该有2GB的储备,而且我们使用2个节点,所以应该有足够的空闲内存。我在 gc.log 中没有发现任何提到的错误。
  • CMS GC 发生的频率如何?它至少释放了一些内存吗?如果你分享你的 GC 日志,我可以看看。
  • 不幸的是,我刚刚发现我在catalina.out中混合了最近半年的所有日志,所以没有办法如何分发gc日志。对这个目录 /var/log/gc.log 没有写权限。

标签: java tomcat garbage-collection jvm heap-memory


【解决方案1】:

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

但是当遇到 java.lang.OutOfMemoryError: Java heap space error 时,这些无法访问的引用应该已经被清除了。您可以通过将 -XX:+HeapDumpOnOutOfMemoryError 添加到您的启动参数来检查这一点,在下一个 OutOfMemoryError 生成时触发堆转储。现在,当您爬取此转储时,您应该不会看到无法访问的对象,至少不会有任何显着大小。

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

【讨论】:

  • For this, you can either try to go ahead with the same memory dump gotten -> 不,无法访问的对象意味着您看不到 GC 根信息。可以比较堆,找不到确切的泄漏。
【解决方案2】:

这个问题可能和这个有关

Java heap overwhelmed by unreachable objects

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

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

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

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

卡洛斯

【讨论】:

    【解决方案3】:

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

    本文详细介绍了pathological finalizer example,但总结一下:

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

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

    还有其他可能的解释,但过度使用finalize() 可能是一个原因。 Effective Java Item 7 strongly discourages finalizers:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-17
      • 2015-08-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-08
      • 2013-12-30
      相关资源
      最近更新 更多