一次使用第三方SVN开源API引起的JVM内存泄漏问题

PS:从我的新浪博客搬过来的文章


 

前段时间发现我负责的版本发布系统出现了一次OutOfMemoryError,很奇怪这个系统日常访问量不大,而且最大heap设置为1G以上,当时没 有重视,只是简单重启了应用,一切又正常了。 过了27天后,偶然发现JVM的old gen已经占用了700MB,当时系统压力很低,顿时觉得事情不妙,一定有内存泄漏了。立即用jmap导出了当时Heap的快照,用mat分析后发现确实 有明显的内存泄漏。发现导致内存占用过多的对象是TimerTask的一个子类TimeoutTask,一共有14013个实例,占用了650MB的 heap空间,这个类来自SVNKit。由于版本发布系统需要自动从SVN上checkout代码,然后编译打包,所以使用了SVNKit的 API(svnkit-1.3.5)。后来下载了源代码发现TimeoutTask应该是负责检测与SVN链接是否timeout的一个task,但很悲 催的发现它竟然会不停的把自己加入到java.util.Timer的调度队列中。下面是节选自DefaultSVNRepositoryPool.java的代码片段:

private class TimeoutTask extends TimerTask {
    public void run() {
        boolean scheduled = false;
        try {
            long currentTime = System.currentTimeMillis();
            synchronized (myInactiveRepositories) {
                for (Iterator repositories = myInactiveRepositories.keySet().iterator(); repositories.hasNext();) {
                    SVNRepository repos = (SVNRepository) repositories.next();
                    long time = ((Long) myInactiveRepositories.get(repos)).longValue();
                    if (currentTime - time >= getTimeout()) {
                        repositories.remove();
                        repos.closeSession();
                    }
                }
                if (myTimer != null) {
                    myTimer.schedule(new TimeoutTask(), 10000);
                    scheduled = true;
                }
            }
        } catch (Throwable th) {
            SVNDebugLog.getDefaultLog().logSevere(SVNLogType.WC, th);
            if (!scheduled && myTimer != null) {
                myTimer.schedule(new TimeoutTask(), 10000);
                scheduled = true;
            }
        }
    }
}

SVNKit为什么要这么设计呢?实际上是为了保持客户端与SVN服务器的链接打开,每10秒一次检测。貌似没有问题,但我们创建SVN客户端时用的是下面的方法:

SVNClientManager client = SVNClientManager.newInstance();

这个模式会每次都创建一个TimeoutTask实例,而TimeoutTask的执行结束前每次又都会创建一个TimeoutTask实例,10 秒后新创建的TimeoutTask会再次执行相同的动作。也就是说我创建过多少SVNClientManager实例就会有多少个 TimeoutTask实例存在,而且永远不会被垃圾回收掉,因为这些TimeoutTask都被加入到了一个全局的 Timer(DefaultSVNRepositoryPool中定义的一个私有静态Timer)对象的调度队列中,从而导致JVM的Old Gen会缓慢增长。
    有两种解决方法,一个是SVNClientManager只创建一次;另一个是SVNClientManager使用完了以后马上执行dispose方法就会释放掉那些TimeoutTask。

 从这个问题我也有一些自己的感受:
    1.第三方API有的虽然功能很强大,但在使用时务必要仔细参考其说明文档,甚至是源代码,否则出了问题会比较棘手。
    2.遇到一个问题一定要有解决到底的精神,如果只是一知半解不想把问题搞清楚,那么你永远只能知道些皮毛。我感觉现在遇到的很多刚毕业参加工作的程序员, 总是把精力放在学习如何使用各种开源框架上,但对这些开源框架的原理和基本实现却并不关心,遇到问题就知道上baidu搜一下现成的答案,而不是去看官方 的文档,其实有些问题各种开源框架的文档写的非常清楚,并且上面会告诉你框架的开发者是怎么做的和这样做的原理。作为合格的程序员,你所关心的不应当是如何使用那些外表华丽的框架,你应当能了解他们的实现原理,并从原理中学习涉及的基础知识,比如HTTP协议,Socket通讯,J2EE的Servlet标准,多线程技术等,知道这些基础知识远比知道怎么简单使用一个框架要有用得多。

转载于:https://www.cnblogs.com/snowboyovo/archive/2013/05/09/3068629.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值