知识就是力量 ——弗朗西斯·培根
by Jerry Chin
开始说明问题之前,先介绍两个概念。
根据 Java Servlet 规范,Servlet 容器中必须为每一个应用提供私有的 Classloader,容器运行期间应用可能关闭或再次部署,相应的 Classloader 将会被 GC 掉。 如果该 Classloader 加载的类或其实例没有被及时回收,将导致 Classloader 内存泄露。
DriverManager 首次初始化首先获取当前上下文类加载器,然后使用该加载器通过 SPI 机制加载并注册所有符合 JDBC 4.0+ 规范的数据库驱动。 JDBC 4.0 之前版本,应用开发者请求数据库连接前必须手动注册数据库驱动,这种机制一定程度减轻了应用开发者的工作量。
自动注册流程如下:
下面用伪代码和时序图来准备表达自动注册流程:
+----------------+ +----------------+ +----------------+ +----------------+
| Container | | Application | | DriverManager | | JDBC Driver |
+----------------+ +----------------+ +----------------+ +----------------+
| Thread.setContextLoader(Application's private Classloader) | |
| init | | |
| ----------------------> | | |
| | Get database connection | |
| |---------------------------> | init(执行自动注册) |
| | |------------ |
| | | | |
| | | V |
| | | CL = Thread.getContextLoader() |
| | | Load all drivers with |
| | | ServiceLocator.load(Driver.class, CL) |
| | | ------------------------------------> |
| | | | 执行初始化,注册到 DriverManger
| | | RegisterDriver() |
| | | <---------------------------------------- |
| | | |
引用链如下(箭头指向被引用对象):
DriverManager --------> Driver Object ------> Driver class -----> ClassLoader -----> 所有此 ClassLoader 加载的类
通常来说, Web 应用运行时其线程上下文类加载器就是其私有的 Classloder,这意味着自动注册流程第 3 步将导致 DriverManager 继续间接引用该 Web 应用的 Classloader 产生内存泄露,除非应用关闭(undeploy)时主动反注册驱动程序,
Apache Tomcat 官方文档详细论述了这个问题,并提出规避措施,有兴趣的话可以继续阅读。
tags: JDBC