Java进程执行tar命令生成压缩包时卡顿问题排查
首先,你遇到的卡顿问题核心原因是Java的Runtime.exec()没有处理子进程的输出缓冲区,和多进程同时打包的关联是间接的——不是多进程本身导致,而是每个未处理输出的tar进程都会因为缓冲区满而阻塞,看起来像是集体卡壳。
为什么会卡住?
你用strace看到的write(1, "./file"..., 66)是关键:tar的-v参数会让它把正在打包的文件路径输出到标准输出(stdout,文件描述符1)。而Java的Process对象默认不会主动读取子进程的stdout/stderr,当输出缓冲区(通常是几KB到几十KB)被填满后,tar进程就会卡在write()调用上,无法继续执行后续的打包操作——因为操作系统会阻止它继续写入,直到缓冲区有空间。
手动执行tar命令没问题,是因为终端shell会自动处理子进程的stdout,实时把内容输出到屏幕,缓冲区不会被填满,tar能一直跑下去。
多进程同时打包的影响
你提到多个进程同时打包不同目录时会一起卡顿,本质是每个tar子进程都面临同样的缓冲区满问题:主Java进程没处理任何一个子进程的输出,所以每个tar都会在自己的缓冲区满时停住,看起来像是互相影响,但实际上它们是各自阻塞在自己的输出写入步骤上。
解决方案
方案1:关闭tar的 verbose 输出(最简单)
直接去掉命令里的-v参数,这样tar不会输出任何打包日志,自然不会触发缓冲区阻塞:
String command = "tar -zcf dest.tar.gz -C dirTotar ."; // 去掉-v参数 Process process = Runtime.getRuntime().exec(command); process.waitFor(); return process.exitValue();
方案2:处理子进程的输出流(需要保留日志时)
如果需要保留tar的verbose输出用于排查,必须在Java代码中单独启动线程读取子进程的InputStream(对应stdout)和ErrorStream(对应stderr),避免缓冲区满:
String command = "tar -zcvf dest.tar.gz -C dirTotar ."; Process process = Runtime.getRuntime().exec(command); // 启动线程读取stdout(tar的verbose日志) new Thread(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { // 可将日志输出到Java日志系统,比如SLF4J // logger.info("Tar打包进度: {}", line); } } catch (IOException e) { e.printStackTrace(); } }).start(); // 启动线程读取stderr(tar的错误信息) new Thread(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { String line; while ((line = reader.readLine()) != null) { // logger.error("Tar打包错误: {}", line); } } catch (IOException e) { e.printStackTrace(); } }).start(); process.waitFor(); return process.exitValue();
注意:一定要同时处理stdout和stderr,因为如果tar有错误输出,stderr缓冲区满了也会导致阻塞。另外Java 5+更推荐用ProcessBuilder替代Runtime.exec(),它的API更灵活,还能通过redirectErrorStream(true)把stderr合并到stdout,减少线程工作量。
内容的提问来源于stack exchange,提问作者noob

