这是一个问题,我一直在努力追查几个月。我有一个Java应用程序正在运行,它处理XML提要并将结果存储在数据库中。一直以来,都出现了很难追踪的间歇性资源问题。
背景:在生产盒(问题最明显的地方)上,我对这个盒子没有特别好的访问权限,并且无法运行Jprofler。这个盒子是一个64位四核的机器,8GB的机器运行Centos 5.2,tomcat 6和java 1.6.0.11。它从这些java-opts开始。
JAVA_OPTS="-server -Xmx5g -Xms4g -Xss256k -XX:MaxPermSize=256m -XX:+PrintGCDetails -
XX:+PrintGCTimeStamps -XX:+UseConcMarkSweepGC -XX:+PrintTenuringDistribution -XX:+UseParNewGC"
技术堆栈如下:
我最接近于再现这个问题的是一台32位的机器,它的内存要求较低。我确实可以控制。我已经用JProfiler研究了它,并修复了许多性能问题(同步问题,预编译/缓存XPath查询,减少线程池,消除不必要的Hibernate预取,以及处理过程中过度热心的“缓存升温”)。
在每一种情况下,分析器都会显示出,由于某种原因,它们占用了大量的资源,并且一旦发生变化,这些资源就不再是主要的资源消耗。
问题是:JVM似乎完全忽略了内存使用设置,填充了所有内存,并变得没有响应。这对于客户来说是一个问题,因为他们期望定期投票(5分钟的基础和1分钟的重试),以及我们的运营团队,他们不断地被告知一个盒子已经没有响应,必须重新启动它。在这个盒子上没有其他重要的东西在运行。
问题出现垃圾收集。我们使用ConcurrentMarkSweep(如上所述)收集器,因为最初的STW收集器导致JDBC超时,并且变得越来越慢。日志显示,随着内存使用量的增加,也就是开始抛出cms故障,并返回到原来的停止世界收集器,然后似乎没有正确地收集。
然而,使用jprofler运行时,“RunGC”按钮似乎可以很好地清理内存,而不是显示出越来越大的内存占用,但是由于我无法将jprofler直接连接到生产框,并且解决已验证的热点似乎不起作用,所以我只能用调整垃圾收集盲区的巫术。
我试过的是:
不幸的是,这个问题偶尔也会出现,它似乎是不可预测的,它可以运行几天甚至一周而没有任何问题,或者它一天可能失败40次,而我唯一能持续捕捉到的事情就是垃圾收集出了问题。
是否有人就下列事项提出任何建议:
(A)为什么JVM在配置为小于6的情况下使用8个物理设备和2GB的交换空间。
(B)对GC调优的引用,它实际上解释或提供了使用高级集合的时间和类型设置的合理示例。
(C)对最常见的java内存泄漏的引用(我理解无人声称的引用,但我指的是库/框架级别的引用,或者数据结构中更多的继承内容,如hashmap)。
感谢您所能提供的所有洞察力。
Emil H:
1)是的,我的开发集群是生产数据的镜像,直到媒体服务器。主要的区别是32/64位和可用RAM的数量,我不能很容易地复制,但是代码、查询和设置是相同的。
2)有一些遗留代码依赖于JaxB,但是在重新排序作业以避免调度冲突时,由于它每天运行一次,所以我已经消除了该执行。主解析器使用XPath查询,这些查询调用java.xml.xpath包。这是一些热点的来源,因为一个查询没有被预编译,两个引用是硬编码的字符串。我创建了一个ThreadSafe缓存(Hashmap),并将对XPath查询的引用分解为最终的静态String,这大大降低了资源消耗。查询仍然是处理的很大一部分,但这应该是因为这是应用程序的主要责任。
3)另外一个注意事项,另一个主要使用者是JAI(从提要中重新处理图像)的图像操作。我不熟悉java的图形库,但据我所知,它们并不是特别漏的。
我能够用VisualVM连接到生产实例,但是它禁用了GC可视化/run-GC选项(尽管我可以在本地查看它)。有趣的是:VM的堆分配遵循Java_选项,而实际分配的堆处于1-1.5 G的舒适状态,而且似乎没有泄漏,但是盒子级监视仍然显示泄漏模式,但它没有反映在VM监视中。这个箱子上没有别的东西了,所以我很为难。
发布于 2018-03-01 15:35:00
那么,我终于找到了导致这个问题的原因,并且我发布了一个详细的答案,以防其他人有这些问题。
我在进程执行时尝试了jmap,但这通常会导致jvm进一步挂起,我将不得不使用--force运行它。这导致堆转储似乎缺少大量数据,或者至少错过了它们之间的引用。为了分析,我尝试了jhat,它提供了大量的数据,但没有太多解释它的方式。其次,我尝试了基于eclipse的内存分析工具(http://www.eclipse.org/mat/),该工具显示堆大部分是与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内存的盒子上也无法分辨。幸运的是,MAT能够产生一些漂亮的图形并显示出一些更好的数据。
这次突出的是一个石英线程占用了6GB堆的4.5GB,其中大部分是休眠状态StatefulPersistenceContext(https://www.hibernate.org/hib_docs/v3/api/org/hibernate /engine/StatefulPersistenceContext.html)。这个类在内部被hibernate用作它的主缓存(我已经禁用了EHCache支持的第二级和查询缓存)。
这个类用于启用大部分hibernate的功能,所以它不能直接禁用(你可以直接解决它,但是spring不支持无状态会话),如果这样做的话,我会很惊讶在成熟的产品中出现重大内存泄漏。那为什么现在漏水?
那么,这是一个事情的组合:石英线程池实例化某些东西是threadLocal,春天正在注入一个会话工厂,这是石英线程生命周期开始时创建一个会话,然后被重用来运行各种使用休眠会话的石英作业。然后Hibernate在会话中缓存,这是它的预期行为。
问题在于线程池永远不会释放会话,因此hibernate保持驻留并在会话的生命周期中维护缓存。由于这是使用spring hibernate模板支持,因此没有明确使用会话(我们使用的是dao - > manager - > driver - > quartz-job hierarchy,dao是通过spring注入休眠配置的,所以操作是直接在模板上完成)。
所以会话永远不会被关闭,hibernate保持对缓存对象的引用,所以它们永远不会被垃圾回收,所以每次运行一个新的作业时,它都会继续填充线程本地的缓存,所以甚至没有任何不同工作之间的分享。此外,由于这是一项写入密集型工作(很少阅读),缓存大部分都浪费了,所以对象不断创建。
解决方案:创建一个显式调用session.flush()和session.clear()的dao方法,并在每个作业开始时调用该方法。
该应用程序已运行了几天,无监视问题,内存错误或重新启动。
感谢大家在这方面的帮助,这是一个相当棘手的错误追踪,因为一切都完全按照它应该做的,但最终3线方法设法解决所有问题。
发布于 2018-03-01 16:40:18
您能在启用JMX的情况下运行生产盒吗?
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=<port>
...
然后附加JConsole,VisualVM?
使用jmap进行堆转储可以吗?
如果是的话,你可以使用JProfiler(你已经拥有),jhat,VisualVM,Eclipse MAT来分析堆转储的泄漏。还比较可能有助于查找泄漏/模式的堆转储。
正如你所提到的雅加达 - 普通人。使用jakarta-commons-logging来保存类加载器时存在问题。为了更好地阅读该检查
https://stackoverflow.com/questions/-100004180
复制相似问题