好吧,我终于找到了导致此问题的原因,并且我发布了详细的答案,以防其他人遇到这些问题。
我在进程运行时尝试了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查询替换了一些休眠操作(主要是在正在处理大型集合的加载和操作-
替换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支持的第二级和查询缓存)。
此类用于启用休眠的大多数功能,因此不能直接禁用它(可以直接解决它,但是spring不支持无状态会话),如果它具有这样的特性,我将感到非常惊讶成熟产品中的主要内存泄漏。那么为什么现在泄漏呢?
好吧,这是综合的:石英线程池实例化为某些东西,它们是threadLocal,spring正在注入一个会话工厂,这是在石英线程生命周期开始时创建一个会话,然后被重用于运行该线程。使用休眠会话的各种石英作业。然后,Hibernate在会话中进行缓存,这是其预期的行为。
然后的问题是线程池从不释放会话,因此休眠处于驻留状态并在会话的生命周期内维护高速缓存。由于这是使用Springs
Hibernate模板支持的,因此没有显式使用会话(我们使用的是dao->管理器->驱动程序->石英作业层次结构,该dao通过spring注入了休眠配置,因此操作是直接在模板上完成)。
因此,会话永远不会关闭,休眠状态是维护对缓存对象的引用,因此它们永远不会被垃圾回收,因此每次运行新作业时,它只会继续填充线程本地的缓存,因此甚至没有不同工作之间的任何共享。另外,由于这是一项写密集型工作(很少读取),因此缓存大部分被浪费了,因此对象不断被创建。
解决方案:创建一个dao方法,该方法显式调用session.flush()和session.clear(),然后在每个作业开始时调用该方法。
该应用程序已经运行了几天,没有任何监控问题,内存错误或重启。
感谢大家对此的帮助,这是一个非常棘手的错误,因为所有事情都按照预期的方式进行了跟踪,但是最后通过3行方法成功解决了所有问题。



