这是我一直试图追踪几个月的问题.我有一个运行java应用程序来处理xml提要并将结果存储在数据库中.存在非常难以追踪的间歇性资源问题.
背景: 在生产盒(问题最明显的地方),我没有特别好的访问框,并且无法运行Jprofiler.那个盒子是64位四核,8GB机器运行centos 5.2,tomcat6和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"
技术堆栈如下:
Centos 64位5.2
Java 6u11
雄猫6
Spring/WebMVC 2.5
Hibernate 3
石英1.6.1
DBCP 1.2.1
Mysql 5.0.45
Ehcache 1.5.0
(当然还有许多其他依赖项,尤其是jakarta-commons库)
我能解决的最接近问题的是32位机器,内存要求较低.我确实有控制权.我已经使用JProfiler探测它并修复了许多性能问题(同步问题,预编译/缓存xpath查询,减少了线程池,删除了不必要的hibernate预取,以及在处理过程中过度热心的"缓存变暖").
在每种情况下,分析器都会将这些资源显示为由于某种原因而占用大量资源,并且一旦发生变化,这些资源就不再是主要资源.
问题: JVM似乎完全忽略了内存使用设置,填满了所有内存并且没有响应.对于面向客户的人来说,这是一个问题,他们期望定期投票(5分钟和1分钟重试),以及我们的运营团队,他们经常被告知箱子已经无响应并且必须重新启动它.这个盒子上没有其他重要的东西.
问题似乎是垃圾收集.我们正在使用ConcurrentMarkSweep(如上所述)收集器,因为原始STW收集器导致JDBC超时并变得越来越慢.日志显示,随着内存使用量的增加,这开始引发cms失败,并回到最初的世界收藏家,然后似乎没有正确收集.
然而,运行jprofiler,"运行GC"按钮似乎很好地清理内存而不是显示增加的占用空间,但由于我无法将jprofiler直接连接到生产盒,并且解决已证实的热点似乎无法正常工作我是留下了调整垃圾收集盲人的伏都教.
我尝试过的:
分析和修复热点.
使用STW,并行和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_ tuning的任何例子在这种情况下使用的参数.
我也在(离线模式)尝试了jprofiler,与jconsole,visualvm连接,但是我似乎找不到任何可以插入我的gc日志数据的东西.
不幸的是,问题也偶尔会出现,它似乎是不可预测的,它可以运行几天甚至一周而没有任何问题,或者它可以在一天内失败40次,而且我唯一可以看到的是一致的是垃圾收集正在起作用.
任何人都可以提出以下建议:
a)为什么JVM在配置为最大值小于6时使用8个物理演出和2 gb交换空间
.b)对GC调整的引用实际上解释或给出了合理的示例什么样的设置使用高级集合.
c)对最常见的java内存泄漏的引用(我理解无人认领的引用,但我的意思是在库/框架级别,或者在数据结构中更像inherenet,比如hashmaps).
感谢您提供的任何和所有见解.
编辑
Emil H:
1)是的,我的开发集群是生产数据的镜像,直到媒体服务器.主要的区别是32/64bit和可用的RAM量,我无法轻易复制,但代码和查询和设置是相同的.
2)有一些遗留代码依赖于JaxB,但在重新排序作业以试图避免调度冲突时,我通常会删除执行,因为它每天运行一次.主解析器使用调用java.xml.xpath包的XPath查询.这是一些热点的来源,其中一个查询没有被预编译,两个对它们的引用都是硬编码的字符串.我创建了一个线程安全缓存(hashmap),并将对xpath查询的引用考虑为最终的静态字符串,从而显着降低了资源消耗.查询仍然是处理的很大一部分,但应该是因为这是应用程序的主要责任.
3)另外一个注释,另一个主要消费者是来自JAI的图像操作(从饲料中重新处理图像).我不熟悉java的图形库,但从我发现它们并没有特别漏洞.
(感谢目前为止的答案,伙计们!)
更新:
我能够使用VisualVM连接到生产实例,但它已禁用GC可视化/运行GC选项(尽管我可以在本地查看).有趣的是:VM的堆分配服从JAVA_OPTS,并且实际分配的堆正好坐在1-1.5 gigs,并且似乎没有泄漏,但是盒级监控仍显示泄漏模式,但它是没有反映在VM监控中.这个盒子上没有别的东西在跑,所以我很难过.
好吧,我终于找到了造成这个问题的问题,我发布了一个详细的答案,以防其他人遇到这些问题.
我在程序运行时尝试了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文件,准确地显示了应用程序的状态.具有讽刺意味的是,这个文件太大了,以至于jhat无法分析它,即使在一个16GB的RAM上也是如此.幸运的是,MAT能够生成一些漂亮的图形并显示更好的数据.
这次突然出现的是单个石英线程占用了6GB堆的4.5GB,其中大部分是一个休眠的StatefulPersistenceContext(https://www.hibernate.org/hib_docs/v3/api/org/hibernate /engine/StatefulPersistenceContext.html).hibernate在内部使用此类作为其主缓存(我已禁用由EHCache支持的第二级和查询缓存).
这个类用于启用hibernate的大部分功能,所以它不能直接禁用(你可以直接解决它,但是spring不支持无状态会话),如果有这样的话我会很惊讶成熟产品中的主要内存泄漏.那么为什么现在泄漏呢?
嗯,这是一个组合的东西:石英线程池实例化某些东西是threadLocal,spring正在注入会话工厂,这是在石英线程生命周期的开始创建一个会话,然后被重用来运行使用hibernate会话的各种石英作业.然后Hibernate在会话中缓存,这是它的预期行为.
问题是线程池永远不会释放会话,所以hibernate保持驻留并维护会话生命周期的缓存.由于这是使用spring hibernate模板支持,没有明确使用会话(我们使用的是dao - > manager - > driver - > quartz-job层次结构,dao通过spring注入了hibernate配置,所以操作是直接在模板上完成).
所以会话永远不会被关闭,hibernate正在维护对缓存对象的引用,因此它们永远不会被垃圾收集,因此每次运行新作业时它都会不断填充线程的本地缓存,所以根本没有不同工作之间的任何分享.此外,由于这是一个写密集型作业(读取很少),缓存主要是浪费,因此对象不断创建.
解决方案:创建一个显式调用session.flush()和session.clear()的dao方法,并在每个作业开始时调用该方法.
该应用程序已运行了几天,没有监控问题,内存错误或重新启动.
感谢大家对此的帮助,这是一个非常棘手的错误,因为一切都在按照预期完成,但最终一个3线方法设法解决了所有问题.