maa*_*nus 8 java webserver tomcat memory-leaks thread-local
标题可能有点强,但让我解释一下我是如何理解会发生什么的.我猜这发生在Tomcat上(引用的消息来自Tomcat),但我不确定了.
TL; DR在底部有一个总结,为什么我声称这是Web服务器的错.
我可能错了(但没有错误的可能性,没有理由问):
ThreadLocalThreadLocal指物体从库ClassLoader网络服务器
如果我理解正确,重新部署后旧的"脏"线程继续被重用.它们ThreadLocal引用了引用它们的旧类,它们ClassLoader指的是整个旧的类层次结构.因此PermGen,随着时间的推移,很多东西都停留在空间中OutOfMemoryError.到目前为止这是对的吗?
我假设有两件事:
因此,在每次重新部署一个完整的线程池更新费一毫秒的一小部分几次每小时,也就是说,还有的时间开销0.0001 * 12/3600 * 100%,即0.000033%.
但是,不是接受这个微小的开销,而是有无数的问题.我的计算错了还是我忽略了什么?
作为警告,我们得到了消息
Web应用程序...使用类型为...的键创建了一个ThreadLocal,并且值为...但在Web应用程序停止时无法将其删除.
应该更好地说明
Web服务器...使用线程池但在停止(或重新部署)应用程序后无法续订.
还是我错了?即使所有线程不时重新创建,时间开销也可以忽略不计.但是ThreadLocal在将它们提供给应用程序之前清除它们就足够了,甚至更快.
有一些真正的问题(最近这个问题),用户无能为力.图书馆作家有时可以而且有时不能.恕我直言,网络服务器可以很容易地解决它.事情发生了并且有原因.所以我责怪唯一一个可以对此采取任何行动的政党.
这个问题的标题比正确的更具挑衅性,但它有其重点.raphw的答案也是如此.这个相关问题有另一个开放的赏金.
我认为Web服务器可以解决它如下:
LastCleanupTimestamp在a中存储a ThreadLocal(对于新线程,它是创建时间)delta,例如1小时)ThreadLocals并设置一个新的LastCleanupTimestamp这将确保没有这样的泄漏存在的时间长于delta加上最长请求的持续时间加上线程周转时间.费用如下:
ThreadLocal每个请求检查一个(即几纳秒)ThreadLocal反射清洁所有s(即,每个delta螺纹一次更多纳秒)DateFormat实例,如果有人仍然使用这样一个可怕的东西).如果最近没有取消部署或重新部署应用程序,可以通过简单地设置thresold来关闭它.
TL; DR它不是创建内存泄漏的Web服务器.是你.
让我首先更明确地陈述问题:ThreadLocal变量通常指的Class是ClassLoader由一个容器的应用程序专门使用的一个实例.当取消部署此应用程序时,ThreadLocal引用将变为孤立状态.由于每个实例保持一个参考其Class和因为每个Class保持一个参考其ClassLoader和因为每个ClassLoader保持到它曾经加载的所有类的引用,未部署应用程序的整个分类不能让垃圾回收和JVM实例遭受内存泄漏.
看看这个问题,你可以优化:
Web应用程序的大多数开发人员都认为第一个更重要,因为第二个可以通过编写好的代码来实现.当重新部署与长期请求同时发生时会发生什么?您无法关闭旧线程池,因为这会中断正在运行的请求.(请求周期可以花费多长时间没有全局定义的最大值.)最后,您需要一个相当复杂的协议,这会带来自己的问题.
该ThreadLocal感应泄漏可以通过但总是写来避免:
myThreadLocal.set( ... );
try {
  // Do something here.
} finally {
  myThreadLocal.remove();
}
这样,你的线程总是干净利落.(在一个侧面说明,这几乎就像创建全局变量:它几乎总是一个可怕的想法有一些Web框架例如像检票,使大量的使用这种像这样的Web框架是可怕的,你什么时候使用.需要同时做的事情,让别人使用起来非常直观,有距离典型的Java趋势每个请求一个线程模型,如与播放和Netty的证明.不要坚持这一反模式.不要使用ThreadLocal谨慎!它几乎总是设计糟糕的标志.)
您应该进一步意识到,ThreadLocal并不总是检测到由此引起的内存泄漏.通过扫描Web服务器的工作线程池中的ThreadLocal变量来检测内存泄漏.如果ThreadLocal找到变量,则变量Class显示它ClassLoader.如果此ClassLoader父项或其父项之一是刚刚取消部署的Web应用程序,则Web服务器可以安全地假设内存泄漏.
但是,假设您String在ThreadLocal变量中存储了一些大的s 数组.Web服务器如何假定此阵列属于您的应用程序?该String.class是当然装入JVM的自举的ClassLoader实例,并不能与特定的Web应用程序相关联.通过删除阵列,Web服务器可能会破坏在同一容器中运行的某些其他应用程序.通过不删除它,Web服务器可能会泄漏大量内存.(这一次,它不是一个ClassLoader和它的Classes泄漏.根据阵列的大小,这种泄漏可能会更糟.)
它变得更糟.这一次,假设您ArrayList在ThreadLocal变量中存储了一个.它ArrayList是Java标准库的一部分,因此加载了系统ClassLoader.同样,没有办法告诉实例属于特定的Web应用程序.但是,这次你ClassLoader和它的所有内容Classes都会泄漏,以及存储在本地线程中的所有类的实例ArrayList.这次,Web服务器甚至无法确定在发现内存泄漏时发生了ClassLoader垃圾收集,因为垃圾收集只能推荐给JVM(通过System#gc())但不强制执行.
更新线程池并不像你想象的那么便宜.
无论何时取消部署应用程序,Web应用程序都不能只丢弃线程池中的所有线程.如果在这些线程中存储了一些值,该怎么办?当Web应用程序回收一个线程时,它应该(我不确定所有Web服务器是否都这样做)找到所有未泄漏的线程局部变量并在替换中重新注册它们Thread.因此,您所说的关于效率的数字将不再适用.
同时,Web服务器需要实现一些逻辑来管理所有线程池的替换,这Thread对于您建议的时间计算都不起作用.(您可能必须处理持久的请求 - 考虑在servlet容器中运行FTP服务器 - 这样这个线程池转换逻辑可能会在很长一段时间内处于活动状态.)
此外,ThreadLocal不是在servlet容器中创建内存泄漏的唯一可能性.
设置关闭挂钩是另一个例子.(不幸的是,它是一个常见的.在这里,你应该在你的应用程序取消部署时手动删除关闭钩子.这个问题不会通过丢弃线程来解决.)关闭钩子是自定义子类的实例,Thread它总是被加载由应用程序的类加载器.
通常,任何保持对子类加载器加载的对象的引用的应用程序都可能会产生内存泄漏.(这通常可以通过Thread#getContextClassLoader().)最后,开发人员不会导致内存泄漏,即使在Java应用程序中,许多开发人员误解了自动垃圾收集,因为没有内存泄漏.(想想Jochua Bloch 着名的堆栈实现示例.)
在这个一般性陈述之后,我想评论一下Tomcat的内存泄漏保护:
Tomcat的也不会答应你检测所有的内存泄漏,但由于它们在各自的维基列出涵盖特定类型的这种泄漏的.Tomcat实际上做了什么:
检查JVM中的每个线程,并且内省Thread和ThreadLocal类的内部结构,以查看ThreadLocal实例或绑定到它的值是否由正在停止的应用程序的WebAppClassLoader加载.
某些版本的Tomcat甚至会尝试弥补泄漏:
Tomcat 6.0.24到6.0.26修改JDK(ThreadLocalMap)的内部结构以删除对ThreadLocal实例的引用,但这是不安全的(参见#48895),因此默认情况下它从6.0.27变为可选和禁用.从Tomcat 7.0.6开始,池的线程将被更新,以便安全地修复泄漏.
但是,您必须正确配置Tomcat才能执行此操作.内存泄漏保护的wiki条目甚至会警告您如何TimerThread在涉及到s 时破坏其他应用程序,或者在启动自己的Threads或ThreadPoolExecutors时或者在为多个Web应用程序使用公共依赖项时如何泄漏内存泄漏.
Tomcat提供的所有清理工作都是最后的选择!你想要在生产代码中没有任何东西.
总结:创建内存泄漏不是Tomcat,它是您的代码.某些版本的Tomcat尝试补偿此类泄漏,如果将其配置为可检测到这些泄漏.但是,您有责任处理内存泄漏,您应该将Tomcat的警告视为修复代码的邀请,而不是重新配置Tomcat来清理您的混乱.如果Tomcat在您的应用程序中检测到内存泄漏,甚至可能会有更多内容泄漏.因此,从应用程序中取出堆和线程转储,找出代码泄漏的位置.
| 归档时间: | 
 | 
| 查看次数: | 1118 次 | 
| 最近记录: |