【Java】IO流未正确关闭导致的读写问题

情景再现

同事写的一段代码,主要功能是生成本地文件并调用脚本将其连同上级文件夹进行打包,应用环境为Linux。但是,在服务器上运行后,发现打包的文件为空文件。

问题排查

因为本地开发环境为Windows,并不能执行shell脚本,所以同事在本地开发测试时认为文件正常生成就基本没问题了,也就没顾及太多将代码提交了。
因为本地正常生成文件,而服务器的压缩包没有文件内容,所以我首先对本地与SVN上的代码进行了比对,排查是否是由于代码问题导致源文件根本就没有生成内容继而导致目标压缩包中的文件也没有内容。

  • 比对本地代码与SVN上的代码后,并无发现异常,排除代码提交问题。
  • 修改了应用服务器上的shell脚本,使其在正常打包后不删除源文件。再次执行相关作业,源文件内容正常,目标压缩包中的文件为空白。排除了代码导致的源文件内容未正常生成。
  • 代码和文件都没有发现问题,源文件也正常生成,那就可能是打包的问题了。在源文件正常存在的情形下,使用相关用户执行该脚本,发现正常打包,文件一切正常。
  • 手工执行脚本,文件正常打包,而应用执行脚本却发生了,打包后文件内容为空白的情况。我又怀疑是权限问题,因为如果没有读写权限的话,cp或是tar也会发生目标文件为空的情况。我在脚本中给源文件夹添加了777的权限,这下总不能再失败了吧。
  • 执行结果与前几次相同,目标压缩包中的文件仍为空白。排除权限问题。
  • 排除到这时候,已经没有了方向。我尝试着对其他文件夹进行打包操作,发现全部都正常打包了,并未发生内容丢失的情况。说明脚本中打包命令的使用是没有问题的。
  • 在目标文件夹放入了其他文件,和之前生成的源文件(名字改掉),再次执行作业,打包的结果显示,只有程序生成的那个文件打包后发生内容丢失的情况。排查方向又再次回来了,打包时文件的状态不对劲。
  • 文件状态不对,又涉及到文件读写,我的第一反应是IO流的处理。但是我们是有代码扫描的,之前她就提过一版专门修复流的问题,是通过的呀?流肯定是关闭了的。
  • 我在本地更新代码后,一眼就看到了问题。IO流的确是被关闭了,但是调用脚本进行打包的时机不对,那个时候流还未关闭。这就导致了,调用脚本在进行打包操作时,源文件的状态是正在被写入当中,服务器在执行tar命令时,读取到的文件是空白的,这也很好地解释了为什么源文件正常,而目标压缩包中文件为空白。也能解释为什么手工执行脚本,一切正常,因为那时候源文件的状态已经是写完成的状态了,不再被其他线程占用。
  • 下面是问题代码的demo
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public static void main(String[] args) throws Exception {
    FileOutputStream fos = null;
    OutputStreamWriter osw = null;
    BufferedWriter bw = null;
    try {
    fos = new FileOutputStream("d:\\a.txt");
    osw = new OutputStreamWriter(fos, "UTF-8");
    bw = new BufferedWriter(osw);
    bw.write("java IO close test");
    // 调用shell脚本进行打包操作
    doTarFile();
    } finally {
    if (bw != null) {
    System.out.println("bw未关闭");
    bw.close();
    }
    if (osw != null) {
    System.out.println("osw未关闭");
    osw.close();
    }
    if (fos != null) {
    System.out.println("fos未关闭");
    fos.close();
    }
    }

    }

问题解决

脚本不做改动,代码中调用脚本的那段代码提出来,放到外层方法的写文件方法之后。