专栏名称: ImportNew
伯乐在线旗下账号,专注Java技术分享,包括Java基础技术、进阶技能、架构设计和Java技术领域动态等。
目录
相关文章推荐
芋道源码  ·  开源实用的一物一码溯源防伪系统 ·  3 天前  
芋道源码  ·  工作中这样用MQ,很香! ·  4 天前  
芋道源码  ·  DeepSeek薪资曝光! ·  5 天前  
Java编程精选  ·  SpringBoot 实现 PDF ... ·  1 周前  
51好读  ›  专栏  ›  ImportNew

ThreadLocal内存泄露

ImportNew  · 公众号  · Java  · 2016-12-06 19:58

正文

(点击上方公众号,可快速关注)


英文:codecentric

译文:ImportNew - LW

如需转载,发送「转载」二字查看说明


我见过许多内存泄漏是由于ThreadLocal的模式使用不当造成的。而鉴于我昨天在我们自己的一个应用程序中遇到了这个问题,我打算写一篇关于此的博客。


Java中的ThreadLocal 变量用于将变量同当前线程绑定,每个线程都有自己独立的ThreadLocal变量。这些变量通常用于保存一些变量的状态信息,譬如用户信息这种在整个应用中都使用的到并且你不想在每个方法中都重新声明。


ThreadLocal 的生命周期和它相应的线程直接关联。如果线程被终止并且被垃圾回收器收集,它相应的ThreadLocal 变量也将会被回收。


内存问题主要发生在当ThreadLocal变量使用在运行在应用服务器上的Java EE应用程序里边时。应用服务器通过使用线程池来管理线程以保证资源安全和提高性能。(参见Tomcat 6 HTTP conncector配置为例)。例如,一个HttpServletRequest发送到应用服务器的ServletEngine,一个空闲的线程将会从线程池中取出并且和servlet的应用逻辑进行连接。如果这个servlet或者它调用的Java类正在使用ThreadLocal变量,这些变量将会和当前的工作线程连接。如果servlet完成并将相应发送给客户端,那么与之连接的线程会被返回到线程池中,以便用来处理其他的请求。这意味着线程对象及其相关联的ThreadLocal变量没有被垃圾回收器收集,因为其线程对象还存在着。


根据池中的线程数量(在运行环境中大于100个线程是正常的)以及ThreadLocal变量中对象的大小,可能会发生致命的内存问题。例如对线程池中的200个线程进行配置以及将ThreadLocal变量的大小设置为5MB,这将会导致有1GB的堆空间被这些变量所占用。这将会导致一个GC的开销并且可能会由于OutOfMemoryError导致JVM崩溃。


在实际情况下,服务器必须每天进行重启以避免OutOfMemoryError问题。为了分析这个问题我们在运行期间拿出一个heapdump(参见最近的博客所做)。我用Eclipse MAT对这个dump进行了分析,它非常迅速地显示出我们存在的Threadlocal内存问题。




上面的截图显示的是dump的支配树。我标记了6个线程,每个线程占用14MB的堆内存。


下一个截图显示了这些线程的具体细节。高内存消耗是ThreadLocal变量造成的结果,它引用了一个DOMParser并且其解析文档占用了总共14MB的大小。因为MAT也可以显示对象的内容,所以我们可以很容易地看到parsers是由于我们的WebServices引起的。 WebService类由JBoss WS生成。Google表明这是jbossws1.2.0版本中的Bug引起的,而这在1.2.1的版本中得到了修复。“DOMUtils并不清除本地线程”。所以我们需要对JBoss AppServer用最新发布的jbossws进行补丁,这样问题就解决了。




这个例子表明,当你在一个应用服务器里使用ThreadLocal时,你必须小心。如果一个被间接引用的ThreadLocal变量不再使用时,对其进行处理是至关重要的。通常我们使用ServletFilters做这个工作。


关注「ImportNew」

看更多 Java 技术精选文章

↓↓