使用ChromeDriver的quit()与close()后仍存在Chrome僵尸进程的问题
Chrome僵尸进程问题排查与解决(CentOS8环境)
问题描述
在CentOS8环境中运行Jar包,通过Selenium 4.7.2搭配ChromeDriver 99实现浏览器链接获取。已在代码中调用driver.close()和driver.quit(),还额外通过端口查询Chrome进程PID并尝试杀死,但程序结束后仍残留大量Chrome僵尸进程。
提供代码
... WebDriver driver = null; List<Integer> pids = new ArrayList<>(); try { driver = new ChromeDriver(service, desiredCapabilities); Integer port = service.getUrl().getPort(); pids = GetChromePidFromPort(port); log.info("detected chrome process id " + pids); driver.manage().window().maximize(); driver.manage().window().setSize(new Dimension(1920, 1080)); driver.get(url); log.info("drive task is done, sleep :" + duration + " ms"); Thread.sleep(duration); return true; } catch (InterruptedException | IOException e) { log.info("chrome download png error"); e.printStackTrace(); } finally { if (driver != null) { driver.close(); driver.quit(); if (!pids.isEmpty()) { killPid(pids); } } }
private static List<Integer> GetChromePidFromPort(Integer port) throws IOException { List<Integer> pidList = new ArrayList<>(); Process process = Runtime.getRuntime().exec("lsof -i:" + port); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line; while ((line = reader.readLine()) != null) { if (line.contains("chrome")) { int pid = Integer.parseInt(line.split("\\s+")[1]); System.out.println("PID: " + pid); pidList.add(pid); } } return pidList; }
进程截图

解决方法
1. 显式停止ChromeDriver服务
Selenium的ChromeDriver退出时,可能没有彻底终止底层的ChromeDriverService。需要在driver.quit()后显式停止服务:
finally { if (driver != null) { try { driver.quit(); } catch (Exception e) { log.warn("Driver quit failed", e); } } // 停止ChromeDriver服务 if (service != null && service.isRunning()) { try { service.stop(); } catch (Exception e) { log.warn("ChromeDriver service stop failed", e); } } }
2. 递归杀死所有Chrome子进程
Chrome会启动多个子进程(渲染、GPU等),这些进程不绑定同一端口,仅杀端口关联进程无法清理干净。可以通过ChromeDriver的父PID递归杀死所有子进程:
- 先获取ChromeDriver的PID(Selenium 4+支持
service.getProcessId()) - 编写递归杀进程方法:
private static void killProcessTree(int parentPid) throws IOException, InterruptedException { // 获取父进程的所有子PID Process pgrep = Runtime.getRuntime().exec("pgrep -P " + parentPid); BufferedReader reader = new BufferedReader(new InputStreamReader(pgrep.getInputStream())); String line; List<Integer> childPids = new ArrayList<>(); while ((line = reader.readLine()) != null) { childPids.add(Integer.parseInt(line)); } reader.close(); pgrep.waitFor(); // 先递归杀死子进程 for (int pid : childPids) { killProcessTree(pid); } // 杀死当前进程 Runtime.getRuntime().exec("kill -9 " + parentPid); log.info("Killed process: " + parentPid); }
在finally块中调用:
if (service != null) { int driverPid = service.getProcessId(); killProcessTree(driverPid); }
3. 添加Chrome启动参数优化进程清理
启动Chrome时添加以下参数,强制进程退出时彻底清理:
ChromeOptions options = new ChromeOptions(); options.addArguments( "--no-sandbox", // 禁用沙箱,CentOS无界面环境必需 "--disable-dev-shm-usage", // 避免/dev/shm空间不足 "--headless=new", // 无界面模式 "--single-process", // 单进程运行,减少进程数量(可选) "--disable-background-networking", "--disable-sync", "--no-first-run", "--safebrowsing-disable-auto-update" ); // 将options传入desiredCapabilities desiredCapabilities.setCapability(ChromeOptions.CAPABILITY, options);
4. 修正进程查询逻辑
原方法仅通过端口查询PID,遗漏大量子进程。改为通过ChromeDriver的PID查询所有关联进程,或者直接使用pkill -f chrome(需谨慎,避免误杀其他Chrome进程):
// 可选:批量杀死所有chrome相关进程(仅限当前任务专属场景) Runtime.getRuntime().exec("pkill -f chrome");
5. 回收Java子进程资源
使用Runtime.exec()后,需调用process.waitFor()和关闭流,避免子进程变成僵尸:
private static List<Integer> GetChromePidFromPort(Integer port) throws IOException, InterruptedException { List<Integer> pidList = new ArrayList<>(); Process process = Runtime.getRuntime().exec("lsof -i:" + port); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line; while ((line = reader.readLine()) != null) { if (line.contains("chrome")) { int pid = Integer.parseInt(line.split("\\s+")[1]); pidList.add(pid); } } reader.close(); process.waitFor(); // 等待进程执行完毕,回收资源 return pidList; }
内容的提问来源于stack exchange,提问作者MasonXu
相关产品推荐
相关产品推荐

