Eclipse内存分析器检测到内存泄漏,求WebappClassLoader内存占用解决方法
我之前处理过类似的Tomcat类加载器内存泄漏问题,结合你给出的堆分析信息(org.apache.catalina.loader.WebappClassLoader @ 0x6c0706e30关联java.util.concurrent.ConcurrentHashMap$Node[]占用近20%内存),给你几个实用的排查和解决方向:
先确认是否和热部署相关
如果你的环境频繁做应用热部署(比如开发环境自动重启),Tomcat的WebappClassLoader很容易因为外部引用无法被GC回收。可以先检查Tomcat的conf/context.xml配置,开启antiResourceLocking="true"和antiJARLocking="true"——这两个配置能减少热部署时的资源锁定问题,但只是缓解手段,不是根治方法。如果是生产环境,建议直接关闭热部署(在conf/server.xml中将autoDeploy和deployOnStartup设为false),减少类加载器的重复创建。定位ConcurrentHashMap的实际持有者
用内存分析工具(比如MAT)深入追踪这个ConcurrentHashMap$Node[]所属的ConcurrentHashMap实例,找到它的引用链。通常这类泄漏的常见源头有:- 第三方框架的静态缓存:比如ORM框架(Hibernate/MyBatis)、序列化框架(Jackson)的类/对象缓存,这些静态缓存会持有WebappClassLoader加载的类实例,导致类加载器无法被回收。
- 未清理的自定义线程池:如果应用内有自定义线程池,在应用卸载时没有关闭,线程的上下文类加载器还是WebappClassLoader,或者线程持有了应用内的对象引用,都会导致泄漏。
- 全局单例对象:如果WebappClassLoader加载的单例对象被系统类加载器加载的全局对象引用,也会死死拽住类加载器不放。
针对性修复方案
- 框架缓存清理:查看对应框架的文档,找到缓存清理的API,在应用的
ServletContextListener的contextDestroyed方法中主动调用清理逻辑。比如MyBatis可以关闭SqlSessionFactory,Hibernate可以销毁SessionFactory,Jackson可以清空类型缓存。 - 线程池优雅关闭:在应用销毁时,手动调用线程池的
shutdown()或shutdownNow()方法,同时将线程的上下文类加载器重置为系统类加载器(Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader()))。 - 检查单例依赖:排查应用内的单例对象,避免它们被WebappClassLoader之外的类加载器持有引用。
- 框架缓存清理:查看对应框架的文档,找到缓存清理的API,在应用的
验证修复效果
修复后,重新做一次堆dump,用内存分析工具确认WebappClassLoader实例是否能被正常GC回收,ConcurrentHashMap$Node[]的内存占用是否回到合理范围。
内容的提问来源于stack exchange,提问作者user9512425

