Spring Boot多线程下WebDriver调用remove()后仍内存泄漏问题
Spring Boot集成WebDriver内存泄漏问题解决
在Spring Boot应用中使用WebDriver时遇到内存泄漏:即便调用driver.quit(),内存仍无法完全释放。当前运行2个独立线程,每次请求时每个线程执行以下步骤:
- 启动驱动
- 导航至网页(示例为google.com)
- 关闭驱动
以下是相关代码:
主类
@SpringBootApplication @ServletComponentScan public class App { public static void main(String[] args) throws IOException { ApplicationContext context = SpringApplication.run(App.class, args); // 通过代码请求端点 RestTemplate restTemplate = context.getBean(RestTemplate.class); restTemplate.getForEntity("http://localhost:8080/hello", Void.class); } }
配置类
@Configuration @EnableAsync public class AsyncConfig { @Bean(name="asyncTaskExecutor") public Executor asyncTaskExecutor(){ System.out.println("配置异步任务"); ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(2); executor.setMaxPoolSize(2); executor.setQueueCapacity(100); executor.setThreadNamePrefix("Async Thread-"); executor.initialize(); return executor; } // RestTemplate配置 @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder.build(); } }
服务类
@Service public class MyService{ private static ThreadLocal<WebDriver> driverThreadLocal = ThreadLocal.withInitial(() -> null); public static WebDriver getDriver() { return driverThreadLocal.get(); } public static void setDriver(WebDriver driver) { driverThreadLocal.set(driver); } public static void quitDriver() { getDriver().quit(); driverThreadLocal.remove(); } @Async("asyncTaskExecutor") public void scrappingtask() throws IOException { System.out.println("请求开始"); System.out.println("线程组: "+Thread.currentThread().getThreadGroup()); System.out.println("线程名称: " + Thread.currentThread().getName()); System.out.println("线程ID: " + Thread.currentThread().getId()); if (getDriver() == null) { System.out.println("*******为线程 "+ Thread.currentThread().getName() +"启动WEBDRIVER"); System.setProperty("webdriver.chrome.driver","你的ChromeDriver路径"); ChromeOptions options = new ChromeOptions(); options.addArguments("--headless"); options.addArguments("--disable-cache"); driverThreadLocal.set(new ChromeDriver(options)); } System.out.println("线程 "+Thread.currentThread().getName()+"的WEBDRIVER会话ID:"+((ChromeDriver)driverThreadLocal.get()).getSessionId()); getDriver().get("https://google.com"); try { Thread.sleep(5000); } catch (InterruptedException e1) { e1.printStackTrace(); } System.out.println("请求结束"); quitDriver(); } }
测试用例
@Controller public class TestCases { @Autowired private MyService myservice; @GetMapping("/hello") public synchronized void runtestcases() throws IOException { int i; for (i = 1; i <= 10; i++) { try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(i); try { myservice.scrappingtask(); } catch (Exception e) { e.printStackTrace(); } } } }
预期堆内存能维持在进程启动时的水平,但实际内存持续增长,VisualVM监控界面显示内存不断上升,堆转储可见相关对象未被正常回收。
问题根源
- ThreadLocal与线程池冲突:线程池核心线程会被复用,即便调用
ThreadLocal.remove(),ChromeDriver的native资源可能未完全释放,线程复用导致旧引用残留。 - ChromeDriver资源未彻底清理:
driver.quit()无法保证销毁所有关联的native进程,headless模式下容易出现进程残留。 - 静态变量与Spring Bean生命周期不兼容:
MyService是Spring托管Bean,但ThreadLocal及相关方法为静态,易引发资源管理混乱。
修复方案
1. 彻底清理WebDriver资源,强制终止Chrome进程
修改quitDriver()方法,确保native进程被销毁:
public static void quitDriver() { WebDriver driver = getDriver(); if (driver != null) { try { driver.quit(); // 针对Windows系统强制终止Chrome进程,其他系统需调整命令 if (driver instanceof ChromeDriver) { String sessionId = ((ChromeDriver) driver).getSessionId().toString(); Runtime.getRuntime().exec("taskkill /F /IM chrome.exe /FI \"WINDOWTITLE eq Chrome_" + sessionId + "\""); } } catch (Exception e) { e.printStackTrace(); } finally { driverThreadLocal.remove(); driver = null; // 显式置空帮助GC回收 } } }
2. 调整ThreadLocal使用方式,避免静态变量冲突
将ThreadLocal改为非静态,由Spring Bean管理:
@Service public class MyService{ private ThreadLocal<WebDriver> driverThreadLocal = ThreadLocal.withInitial(() -> null); public WebDriver getDriver() { return driverThreadLocal.get(); } public void setDriver(WebDriver driver) { driverThreadLocal.set(driver); } public void quitDriver() { // 实现同上 } @Async("asyncTaskExecutor") public void scrappingtask() throws IOException { // 原有逻辑,去掉静态调用 ... } }
3. 配置线程池核心线程超时回收
ThreadPoolTaskExecutor默认核心线程不回收,设置超时策略避免线程长期持有引用:
@Bean(name="asyncTaskExecutor") public Executor asyncTaskExecutor(){ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(2); executor.setMaxPoolSize(2); executor.setQueueCapacity(100); executor.setThreadNamePrefix("Async Thread-"); executor.setAllowCoreThreadTimeOut(true); // 允许核心线程超时 executor.setKeepAliveSeconds(60); // 闲置60秒后回收核心线程 executor.initialize(); return executor; }
4. 优化ChromeOptions减少资源占用
添加更多参数降低内存消耗:
ChromeOptions options = new ChromeOptions(); options.addArguments("--headless=new"); // 新版headless模式,资源占用更低 options.addArguments("--disable-gpu"); options.addArguments("--no-sandbox"); options.addArguments("--disable-dev-shm-usage"); // 解决/dev/shm内存不足问题 options.addArguments("--single-process"); // 单进程模式,减少进程数量 options.addArguments("--disable-cache"); options.addArguments("--disable-application-cache");
验证方法
用VisualVM重新监控内存变化,多次执行请求后观察堆内存是否回落至初始水平,同时检查系统进程中无残留Chrome进程。
内容的提问来源于stack exchange,提问作者Yuri V.
相关产品推荐
相关产品推荐

