gpt4 book ai didi

java - 跟踪 Java 中的内存泄漏/垃圾收集问题

转载 作者:行者123 更新时间:2023-12-02 00:54:51 25 4
gpt4 key购买 nike

这是我几个月来一直试图追查的问题。我有一个 Java 应用程序正在运行,它处理 xml 提要并将结果存储在数据库中。存在很难追踪的间歇性资源问题。

背景:
在生产箱(问题最明显的地方)上,我没有特别好的接触箱,并且无法运行 Jprofiler。那台机器是一台运行 centos 5.2、tomcat6 和 java 1.6.0.11 的 64 位四核 8GB 机器。它从这些 java-opts 开始

JAVA_OPTS="-server -Xmx5g -Xms4g -Xss256k -XX:MaxPermSize=256m -XX:+PrintGCDetails -
XX:+PrintGCTimeStamps -XX:+UseConcMarkSweepGC -XX:+PrintTenuringDistribution -XX:+UseParNewGC"

技术栈如下:
  • Centos 64 位 5.2
  • Java 6u11
  • Tomcat 6
  • Spring /WebMVC 2.5
  • hibernate 3
  • quartz 1.6.1
  • DBCP 1.2.1
  • mysql 5.0.45
  • Ehcache 1.5.0
  • (当然还有许多其他依赖项,特别是 jakarta-commons 库)

  • 我能得到的最接近重现问题的是内存要求较低的 32 位机器。我确实可以控制。我已经用 JProfiler 对其进行了彻底的探索,并修复了许多性能问题(同步问题、预编译/缓存 xpath 查询、减少线程池、删除不必要的 hibernate 预取以及处理过程中过度热心的“缓存预热”)。

    在每种情况下,分析器都显示这些由于某种原因占用了大量资源,并且一旦发生变化,这些就不再是主要的资源 pig 。

    问题:
    JVM 似乎完全忽略了内存使用设置,填满了所有内存并变得无响应。这是面向客户的问题,他们希望定期轮询(以 5 分钟为基础和 1 分钟重试),以及我们的运营团队,他们不断收到通知说某个盒子变得无响应并必须重新启动它。在这个盒子上没有其他重要的运行。

    问题似乎是垃圾收集。我们正在使用 ConcurrentMarkSweep(如上所述)收集器,因为最初的 STW 收集器会导致 JDBC 超时并且变得越来越慢。日志显示,随着内存使用量的增加,开始抛出 cms 故障,并返回到原始的 stop-the-world 收集器,然后似乎无法正确收集。

    但是,使用 jprofiler 运行时,“运行 GC”按钮似乎很好地清理了内存,而不是显示出不断增加的占用空间,但是由于我无法将 jprofiler 直接连接到生产盒,并且解决经过验证的热点似乎不起作用我留下了盲目调整垃圾收集的巫术。

    我试过的:

  • 分析和修复热点。
  • 使用 STW、Parallel 和 CMS 垃圾收集器。
  • 以 1/2、2/4、4/5、6/6 的增量运行最小/最大堆大小。
  • 使用 permgen 空间以 256M 的增量运行,最高可达 1Gb。
  • 以上多种组合。
  • 我还咨询了 JVM [调优引用](http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html),但找不到任何解释这种行为的内容或任何 _which_ 调优示例在这种情况下使用的参数。
  • 我也(失败)在离线模式下尝试了 jprofiler,与 jconsole、visualvm 连接,但我似乎找不到任何可以解释我的 gc 日志数据的东西。

  • 不幸的是,问题也偶尔出现,它似乎是不可预测的,它可以运行几天甚至一周都没有任何问题,或者一天可能会失败 40 次,而我似乎唯一能始终捕获的是垃圾收集正在起作用。

    任何人都可以就以下方面提供任何建议:
    a) 当 JVM 配置为最大小于 6 时,为什么要使用 8 个物理 gig 和 2 gb 交换空间。
    b) 对 GC 调优的引用,它实际上解释或给出了合理的示例,说明何时以及使用高级集合的设置类型。
    c) 对最常见的 java 内存泄漏的引用(我理解无人认领的引用,但我的意思是在库/框架级别,或者数据结构中更内在的东西,如哈希图)。

    感谢您提供的任何和所有见解。

    编辑
    埃米尔·H:
    1) 是的,我的开发集群是生产数据的镜像,一直到媒体服务器。主要区别是 32/64 位和可用 RAM 量,我无法轻松复制,但代码、查询和设置是相同的。

    2) 有一些遗留代码依赖于 JaxB,但为了重新排序作业以避免调度冲突,我通常消除了该执行,因为它每天运行一次。主解析器使用调用 java.xml.xpath 包的 XPath 查询。这是一些热点的来源,一个是查询没有被预编译,两个是对它们的引用是硬编码的字符串。我创建了一个线程安全缓存(hashmap)并将对 xpath 查询的引用分解为最终的静态字符串,这显着降低了资源消耗。查询仍然是处理的很大一部分,但应该是因为这是应用程序的主要职责。

    3)另外一个注意事项,另一个主要消费者是来自 JAI 的图像操作(从提要中重新处理图像)。我不熟悉 java 的图形库,但据我所知,它们并不是特别容易泄漏。

    (感谢到目前为止的答案,伙计们!)

    更新:
    我能够使用 VisualVM 连接到生产实例,但它禁用了 GC 可视化/运行 GC 选项(尽管我可以在本地查看它)。有趣的是:VM 的堆分配遵循 JAVA_OPTS,实际分配的堆舒适地坐在 1-1.5 gigs,并且似乎没有泄漏,但是盒子级别的监控仍然显示泄漏模式,但它是未反射(reflect)在 VM 监控中。这个盒子上没有其他东西在运行,所以我很难过。

    最佳答案

    好吧,我终于找到了导致此问题的问题,并且我将发布详细答案,以防其他人遇到这些问题。

    我在进程运行时尝试了 jmap,但这通常会导致 jvm 进一步挂起,我必须使用 --force 运行它。这导致堆转储似乎丢失了大量数据,或者至少丢失了它们之间的引用。为了分析,我尝试了 jhat,它提供了很多数据,但在如何解释它的方式上并不多。其次,我尝试了基于eclipse的内存分析工具(http://www.eclipse.org/mat/),发现heap主要是与tomcat相关的类。

    问题是 jmap 没有报告应用程序的实际状态,而只是在关闭时捕获类,这些类主要是 tomcat 类。

    我又试了几次,注意到模型对象的数量非常高(实际上是数据库中标记为公开的 2-3 倍)。

    我使用它分析了慢查询日志,以及一些不相关的性能问题。我尝试了超延迟加载( http://docs.jboss.org/hibernate/core/3.3/reference/en/html/performance.html ),并用直接 jdbc 查询替换了一些 hibernate 操作(主要是在处理大型集合的加载和操作的地方——jdbc 替换只是直接在连接表上工作) ,并替换了 mysql 正在记录的其他一些低效查询。

    这些步骤改进了前端性能,但仍然没有解决泄漏问题,应用程序仍然不稳定并且无法预测。

    最后,我找到了选项: -XX:+HeapDumpOnOutOfMemoryError 。这最终产生了一个非常大(~6.5GB)的 hprof 文件,它准确地显示了应用程序的状态。具有讽刺意味的是,文件太大了,即使在一个装有 16GB 内存的盒子上,jhat 也无法对其进行分析。幸运的是,MAT 能够生成一些漂亮的图表并显示一些更好的数据。

    这次突出的是单个 quartz 线程占用了 6GB 堆中的 4.5GB,其中大部分是 hibernate 状态的 StatefulPersistenceContext ( https://www.hibernate.org/hib_docs/v3/api/org/hibernate/engine/StatefulPersistenceContext.html )。这个类在内部被 hibernate 用作它的主缓存(我已经禁用了 EHCache 支持的二级缓存和查询缓存)。

    这个类用于启用hibernate的大部分功能,因此不能直接禁用它(您可以直接解决它,但spring不支持无状态 session ),如果它有这样的功能,我会感到非常惊讶成熟产品中的主要内存泄漏。那为什么现在漏水了?

    嗯,这是一个组合:
    quartz 线程池实例化某些东西是 threadLocal,spring 正在注入(inject)一个 session 工厂,它在 quartz 线程生命周期开始时创建一个 session ,然后被重用以运行使用 hibernate session 的各种 quartz 作业。 Hibernate 然后在 session 中缓存,这是它的预期行为。

    问题是线程池从不释放 session ,因此 hibernate 状态一直驻留并在 session 的生命周期内维护缓存。由于这是使用 springs hibernate 模板支持,因此没有明确使用 session (我们使用的是 dao -> manager -> driver ->quartz-job 层次结构,dao 通过 spring 注入(inject)了 hibernate 配置,因此操作是直接在模板上完成)。

    所以 session 永远不会被关闭,hibernate 维护对缓存对象的引用,所以它们永远不会被垃圾收集,所以每次运行新作业时,它只会不断填充线程本地的缓存,所以甚至没有不同作业之间的任何共享。此外,由于这是一项写入密集型工作(很少读取),因此缓存大部分都被浪费了,因此对象不断被创建。

    解决方案:创建一个显式调用 session.flush() 和 session.clear() 的 dao 方法,并在每个作业开始时调用该方法。

    该应用程序已经运行了几天,没有出现监控问题、内存错误或重启。

    感谢大家对此的帮助,这是一个非常棘手的错误,因为一切都按照预期进行,但最终 3 行方法设法解决了所有问题。

    关于java - 跟踪 Java 中的内存泄漏/垃圾收集问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1071631/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com