You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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监控界面显示内存不断上升,堆转储可见相关对象未被正常回收。


问题根源

  1. ThreadLocal与线程池冲突:线程池核心线程会被复用,即便调用ThreadLocal.remove(),ChromeDriver的native资源可能未完全释放,线程复用导致旧引用残留。
  2. ChromeDriver资源未彻底清理:driver.quit()无法保证销毁所有关联的native进程,headless模式下容易出现进程残留。
  3. 静态变量与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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 23:25:53