oracle jdbc驱动不卸载会导致permgen oom,本质是classloader泄漏:oracledriver被webappclassloader加载后注册到由bootstrapclassloader管理的drivermanager中,形成反向引用链,使类加载器无法回收,其加载的类永久驻留permgen。
oracle jdbc驱动在web应用中不卸载,会直接导致permgen oom——这不是配置不够的问题,而是类加载器泄漏(classloader leak)的典型表现。hotspot jvm 7/8 的 permgen 区域无法回收被 webappclassloader 加载的 oracle.jdbc.driver.oracledriver 及其关联的类(如 oracle.sql.*、oracle.net.*),只要该类加载器还被引用,整块类元数据就永远驻留内存。
为什么Web容器重启后PermGen持续增长
每次热部署或应用重加载,Tomcat 会新建一个 WebAppClassLoader 实例去加载你的应用,包括 ojdbc.jar 中的类。但 Oracle 驱动在初始化时会向 java.sql.DriverManager 注册自己(调用 DriverManager.registerDriver(new OracleDriver())),而 DriverManager 是由系统类加载器(BootstrapClassLoader)加载的,它持有对所有已注册 Driver 实例的强引用。由于 OracleDriver 实例由 WebAppClassLoader 加载,系统类加载器 → Driver 实例 → WebAppClassLoader 形成一条“反向引用链”,导致该类加载器无法被 GC 回收,其加载的所有类(含大量内部类、常量池项)永久滞留 PermGen。
- 现象:连续 5 次 redeploy 后,
jstat -permstat显示已加载类数量翻倍,java.lang.OutOfMemoryError: PermGen space几乎必然发生 - 关键点:
DriverManager.deregisterDriver()不会自动触发;JDBC 4.0+ 的ServiceLoader自动注册机制(META-INF/services/java.sql.Driver)同样不会自动清理 - 验证方式:用
jmap -histo:live <pid></pid>查看oracle.开头的类实例数是否随 redeploy 累积
如何在Web应用关闭时强制卸载Oracle驱动
必须在 ServletContextListener.contextDestroyed() 或 Spring 的 DisposableBean.destroy() 中显式反注册,并清除线程上下文类加载器残留。仅靠 System.setProperty("jdbc.drivers", "") 无效。
- 调用
DriverManager.deregisterDriver(DriverManager.getDrivers().nextElement())—— 但要注意遍历需用Enumeration<driver></driver>并捕获SQLException,因为某些驱动可能已失效 - 手动清空
DriverManager内部的CopyOnWriteArrayList<driverinfo></driverinfo>(JDK 7/8)需反射访问,不推荐;稳妥做法是只处理已知 Oracle 驱动实例 - 更可靠的做法:获取当前 WebAppClassLoader 加载的所有
Driver子类实例,逐个 deregister。示例代码核心逻辑:
for (Enumeration<driver> drivers = DriverManager.getDrivers(); drivers.hasMoreElements();) {
Driver driver = drivers.nextElement();
if (driver.getClass().getName().startsWith("oracle.") &&
driver.getClass().getClassLoader() == getClass().getClassLoader()) {
try { DriverManager.deregisterDriver(driver); }
catch (SQLException ignored) {}
}
}</driver>
- 额外动作:调用
Thread.currentThread().setContextClassLoader(null),防止线程池中存活线程持有所属 WebApp 的类加载器
比卸载更根本的规避方案
PermGen 在 JDK 8 中已被 Metaspace 替代,但问题本质未变——类加载器泄漏依然会导致 Metaspace OOM(错误信息变为 java.lang.OutOfMemoryError: Metaspace)。因此,不能只依赖升级 JDK。
- 使用连接池(如 HikariCP、Druid)并配置
driver-class-name: oracle.jdbc.OracleDriver,它们通常内置驱动卸载钩子;但需确认版本(HikariCP ≥ 2.7.0 才支持) - 将
ojdbc.jar移出WEB-INF/lib,放到 Tomcat 的lib/目录下,让系统类加载器统一加载——这样驱动与 WebApp 生命周期解耦,避免 WebAppClassLoader 加载它 - 禁用 JDBC 4.0 的自动服务发现:在
WEB-INF/web.xml中添加<context-param><param-name>org.apache.tomcat.jdbc.pool.initSQL</param-name><param-value></param-value></context-param>无用;真正有效的是在META-INF/MANIFEST.MF中为 ojdbc.jar 添加Implementation-Title: Oracle JDBC Driver并配合 Tomcat 的antiJARLocking="true",但这只是辅助 - JDK 9+ 用户注意:
javax.sql.DataSource推荐用模块化方式加载,DriverManager已标记为 deprecated,应优先走DataSource+ JNDI 路径
真正难处理的不是怎么写 deregister 代码,而是确保它一定被执行——比如应用崩溃退出、kill -9、容器异常终止时,contextDestroyed 根本不会触发。所以生产环境必须配 -XX:MaxMetaspaceSize(JDK 8+)或 -XX:MaxPermSize(JDK 7),并监控 LoadedClassCount 指标趋势,把泄漏控制在可接受的 redeploy 次数内。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











