跟踪Java中的内存泄漏/垃圾收集问题跟踪Java中的内存泄漏/垃圾收集问题(Tracking do

2019-05-12 14:51发布

这是我一直在努力,现在追查了几个月的问题。 我有一个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
  • Tomcat的6
  • 春/ WebMVC 2.5
  • Hibernate 3的
  • 石英1.6.1
  • DBCP 1.2.1
  • MySQL的5.0.45
  • 1.5.0的Ehcache
  • (当然主机其他相关性,特别是雅加达公共库)

我能得到的再生该问题最接近的是一个32位的机器以较低的内存需求。 我有没有控制权。 我已经用它探测的JProfiler死亡和修正了许多性能问题(同步问题,预编译/缓存XPath查询,减少了线程池,并去除不必要的休眠预取和过分热心“缓存变暖”处理过程中)。

在每种情况下,探查显示这些为占用大量的资源用于一个或那样的原因,而这些不再是主要资源的大量占用,一旦改变走了进去

问题:在JVM似乎完全忽略了内存使用情况设置,填补所有的记忆,变得没有反应。 这是面向客户端的问题,谁需要一个定期轮询(5分钟计算和1分钟的重试),以及对我们的运营团队,谁是不断通知一个盒子已变得没有响应并需要重新启动。 没有什么别的显著运行此框。

这个问题似乎是垃圾收集。 我们使用ConcurrentMarkSweep(如上所述)收集,因为原来的STW收集器是导致JDBC超时和变得越来越慢。 该日志显示了作为内存使用量的增加,也就是开始扔CMS故障,并踢回到原来停止的世界收藏家,然后似乎不能正常采集。

然而,随着JProfiler的运行,“运行GC”按钮,似乎清理内存很好,而不是显示越来越多的足迹,但因为我不能直接连接的JProfiler到生产中,并解决证实热点似乎并不奏效,我剩下的调整垃圾回收盲目的巫术。

我曾尝试:

  • 剖析和固定的热点。
  • 使用STW,并行和CMS的垃圾收集器。
  • 以1 / 2,2 / 4,4 / 5,6 / 6的增量最小/最大的堆大小运行。
  • 在256M增量PermGen的空间中运行高达1GB。
  • 上述的多种组合。
  • 我也咨询了JVM [调整参考](http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html),但不能真正找到任何解释这种行为或_which_调整的任何实例参数,在这样的情况下使用。
  • 我也(失败)的JProfiler尝试在离线模式下,与JConsole的,VisualVM的连接,但我似乎无法找到任何会interperet我GC日志数据。

不幸的是,这个问题也弹出偶尔,它似乎是不可预知的,它可以在无需任何问题,几天甚至一个星期运行,也可以在一天失败40次,我唯一能似乎一直是赶上垃圾收集出毛病了。

谁能给任何意见,以:
一)为什么一个JVM使用8场物理音乐会和2 GB的交换空间时,它被配置为最大出在小于6。
二)GC优化的参考,其实解释或提供时,什么样的设置与使用高级藏品的合理例子。
c)中最常见的Java内存泄漏(我明白无人认领的引用,但我的意思是在图书馆/框架的水平,或更多的东西inherenet在数据结构,像包含HashMap)的引用。

感谢您的任何和所有的见解,你可以提供。

编辑
埃米尔·H:
1)是的,我的发展集群是生产数据的镜像,下至媒体服务器。 主要的区别是在32位/ 64位和可用RAM,这是我不能复制很容易的量,但代码和查询和设置是相同的。

2)有一些遗留代码依赖于JAXB,但在重新排序的工作,以尽量避免安排上的冲突,我有一个执行一般消除,因为它每天运行一次。 主要解析器使用哪个呼叫下降到java.xml.xpath包XPath查询。 这是一些热点的来源,一个查询没有被预编译和两个他们的引用是在硬编码字符串。 我创建了一个线程缓存(HashMap的),并分解到XPath查询引用是最终静态字符串,它显著降低资源消耗。 该查询仍是处理的很大一部分,但它应该是因为它是应用程序的主要责任。

3)一个附加的说明,其他初级消费者是从JAI图像操作(再处理图像从进料)。 我不熟悉Java的图形库,但是从我发现他们不是特别渗漏。

(谢谢你的答案至今,乡亲!)

更新:
我能够连接到与VisualVM的生产实例,但它已经禁用GC可视化/运行GC选项(虽然我可以在本地查看)。 有趣的事:虚拟机的堆分配服从JAVA_OPTS,实际分配的堆在1-1.5演出坐在舒适,而且似乎不被泄露,但框级监控仍显示泄漏模式,但它是没有反映在VM监控。 没有什么别的在这个机器上运行,所以我难倒。

Answer 1:

嗯,我终于发现,造成这个问题,和我张贴的情况下,别人有这些问题的一个细节的答案。

我试图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不能anaylze它,即使与RAM和16GB的盒子。 幸运的是,MAT是能够产生一些漂亮的图形和表现出一些更好的数据。

这时候什么伸出了一个石英线程被占用了4.5GB堆的6GB的,和广大的,这是一个休眠StatefulPersistenceContext( https://www.hibernate.org/hib_docs/v3/api/org/hibernate /engine/StatefulPersistenceContext.html )。 此类用于通过休眠内部作为其主高速缓存(我已经禁用第二级和查询由高速缓存的EHCache支持)。

这个类是用来使大多数的休眠的特性,因此它不能直接禁用(你可以解决它直接,但春天不支持无状态会话),我会感到非常惊讶,如果这有这样一个大内存泄漏在一个成熟的产品。 那么,为什么现在漏水?

嗯,这是一个事物的组合:石英线程池某些事情是ThreadLocal的,春天注入会话中的工厂,这是在石英线程的生命周期,然后被重复使用运行开始创建会话实例各种石英作业所使用Hibernate的Session。 休眠然后在会话中,这是它的预期行为缓存。

接着的问题是,线程池从未释放会话,因此休眠住居民,并保持该会话的生命周期中的高速缓存。 由于这是使用弹簧冬眠模板支持,但没有明确的使用会话(我们使用的是道 - >管理 - >驱动程序 - >石英作业层次,在DAO被注入到春季休眠CONFIGS,所以操作直接在模板完成)。

因此,会议从来没有被关闭,休眠被保持到缓存对象的引用,所以他们从来没有被垃圾收集,所以每次一个新的工作运行它将不断填满缓存本地线程,所以甚至没有在不同的工作之间的共享。 此外,由于这是一个写密集型任务(很少阅读),高速缓存被大部分被浪费,所以一直得到创建的对象。

解决方案:创建明确要求调用Session.flush()和session.clear()一个DAO方法,并调用该方法,在每个作业的开始。

该应用程序已被现在正在运行了几天,没有监控的问题,内存错误或重新启动。

感谢大家对这样的服务,这是一个相当棘手的bug追踪,因为一切都在做什么它应该,但最后一个3线方法设法解决所有问题。



Answer 2:

您可以运行生产彩盒启用JMX?

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=<port>
...

监控和管理使用JMX

然后用JConsole的,附加的VisualVM ?

它是确定做一个堆转储JMAP ?

如果是的话,你可以再分析与JProfiler的泄漏堆转储(你已经有了), 与jHat ,VisualVM的, Eclipse的MAT 。 也比较堆转储,这可能有助于找到泄漏/模式。

正如你所提到的雅加达公共资源。 使用与握住类加载器雅加达共享记录时出现了问题。 对于该检查的好读

一天内存泄漏猎人的生活 ( release(Classloader)



Answer 3:

这似乎是比其他堆内存泄漏,你提到堆保持稳定。 一个经典的候选PermGen的(永久代)所组成的两两件事:加载的类对象和实习字符串。 既然你报告已经与VisualVM的连接,你应该能够显得装入类的数量,如果有继续加载类(重要的VisualVM也说明以往的类加载总量的增加,这是好的,如果这个上升,但在一定时间后所装入类的量应该稳定)。

如果它变成是PermGen的泄漏然后调试变得棘手,因为工装PermGen的分析相比,堆是相当缺乏。 最好的办法是开始反复在服务器上的一个小脚本调用(每小时):

jmap -permstat <pid> > somefile<timestamp>.txt

JMAP与参数将产生加载类的概述与字节大小的估计在一起,此报告可帮助您识别,如果某些类没有得到卸载。 (注:与我的意思是进程ID,应该是一些产生的时间戳来区分的文件)

一旦你确定了一些类如加载和卸载不是你可以精神上揣摩出这些可能产生,否则就可以使用与jHat分析与JMAP突降产生的垃圾场。 我会继续,对于未来的更新您可能需要的信息。



Answer 4:

我会寻找直接分配字节缓冲区。

根据JavaDoc。

直接字节缓冲区可以通过调用这个类的allocateDirect工厂方法来创建。 通过此方法返回的缓冲区通常比非直接缓冲区略高的分配和回收成本。 直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此在应用程序的内存占用他们的影响可能不会很明显。 因此,建议直接缓冲区必须为该受基础系统的本机I / O操作大,寿命长的缓冲区主要分配。 一般来说,最好只分配直接缓冲区时,它们产生的程序性能可测量的收益。

也许Tomcat的代码使用该做的I / O; 配置Tomcat来使用不同的连接器。

如果做不到这一点,你可以有一个线程,定期执行的System.gc()。 “-XX:+ ExplicitGCInvokesConcurrent”可能是一个有趣的选择尝试。



Answer 5:

任何JAXB? 我发现,JAXB是烫发空间填充。

另外,我发现visualgc的 ,现在随JDK 6,就是看发生了什么事情在记忆的好方法。 它显示了伊甸园,辈分,烫发空间和GC精美的瞬态行为。 所有你需要的是进程的PID。 也许,这将有助于当你在JProfile工作。

约在春节跟踪/日志方面是什么? 也许你可以写一个简单的方面,声明应用它,做一个穷人的探查的方式。



Answer 6:

“不幸的是,这个问题也弹出偶尔,它似乎是不可预知的,它可以在无需任何问题,几天甚至一个星期运行,也可以在一天失败40次,我唯一能似乎一直追赶是垃圾回收行动起来。”

听起来,这势必被执行每天最多40次,然后不再几天的使用情况。 我希望,你不只是只跟踪症状。 这一定是什么东西,你可以通过跟踪应用程序的参与者(用户,工作,服务)的行动缩小。

如果发生这种情况的XML进口,你应该比较40个崩溃一天的数据,即在零碰撞天进口的XML数据。 也许这是某种形式的逻辑问题,那你不觉得你的代码中,只有。



Answer 7:

我有同样的问题,有几处不同..

我的技术如下:

Grails的2.2.4

tomcat7

石英插件 1.0

我用我的应用二数据源。 这是一个特殊性决定到错误导致..

另一个要考虑的是,石英插件,在石英线程注入Hibernate的Session,就像@liam说,和石英线程还活着,直到我完成申请。

我的问题是关于Grails的错误ORM与方式插件处理会话和我的两个数据源相结合。

石英插件有一个监听器初始化和销毁​​的Hibernate Session

public class SessionBinderJobListener extends JobListenerSupport {

    public static final String NAME = "sessionBinderListener";

    private PersistenceContextInterceptor persistenceInterceptor;

    public String getName() {
        return NAME;
    }

    public PersistenceContextInterceptor getPersistenceInterceptor() {
        return persistenceInterceptor;
    }

    public void setPersistenceInterceptor(PersistenceContextInterceptor persistenceInterceptor) {
        this.persistenceInterceptor = persistenceInterceptor;
    }

    public void jobToBeExecuted(JobExecutionContext context) {
        if (persistenceInterceptor != null) {
            persistenceInterceptor.init();
        }
    }

    public void jobWasExecuted(JobExecutionContext context, JobExecutionException exception) {
        if (persistenceInterceptor != null) {
            persistenceInterceptor.flush();
            persistenceInterceptor.destroy();
        }
    }
}

就我而言, persistenceInterceptor实例AggregatePersistenceContextInterceptor ,和它有一个列表HibernatePersistenceContextInterceptor 。 为每个数据源。

每opertion做AggregatePersistenceContextInterceptor其传递给HibernatePersistence,无需任何修改或处理。

当我们调用init()HibernatePersistenceContextInterceptor他增加下面的静态变量

private static ThreadLocal<Integer> nestingCount = new ThreadLocal<Integer>();

我不知道,静态计数pourpose。 我只知道他它的递增,因为两次,每一个数据源, AggregatePersistence实现。

直到这里我只解释cenario。

这个问题是现在...

当我的石英作业完成,插件调用监听器来冲洗和破坏的Hibernate Session,就像你可以在源代码中看到SessionBinderJobListener

冲洗完全发生,但不破坏,因为HibernatePersistence ,做一个验证之前关闭Hibernate的Session ......它考察nestingCount看看是否值grather比1。如果答案是肯定的,他不会关闭会话。

简化是什么做的休眠:

if(--nestingCount.getValue() > 0)
    do nothing;
else
    close the session;

这是我的内存泄漏的基地。石英线程仍然活着,在会话中使用的所有对象,因为Grails的ORM不关闭会话,因为一个错误造成的,因为我有两个数据源。

为了解决这个问题,我自定义监听器,以清晰的通话之前破坏,并呼吁摧毁两次,(每个数据源)。 确保我的会议是明确和破坏,如果破坏失败,他至少清楚。



文章来源: Tracking down a memory leak / garbage-collection issue in Java