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

Spring Boot SFTP转S3任务后内存/磁盘未释放,疑资源未正确关闭

内存与磁盘占用未回落的资源泄漏问题排查与修复

你的代码确实存在几处资源未正确清理的问题,导致任务完成后内存和磁盘占用无法回落:

问题点及修复方案

  • 临时文件未主动清理:tempFile.deleteOnExit()仅在JVM进程退出时才会删除文件,但Cloud Foundry环境下应用进程不会频繁退出,这会导致临时文件持续占用磁盘空间。需要在文件上传S3完成后主动删除临时文件,同时处理删除时的异常。
  • InputStream未通过try-with-resources管理:当前手动调用inputStream.close(),但如果IOUtils.copy或s3bucket.putObject抛出异常,close()可能无法执行,导致SFTP输入流资源泄漏,进而占用内存。
  • 重复关闭流:try (FileOutputStream outputStream = ...)已经会自动关闭输出流,手动调用outputStream.close()和outputStream.flush()属于多余操作,甚至可能引发IOException。

修复后的代码

Vector<ChannelSftp.LsEntry> files = sftpChannel.ls("*.csv");
if (!files.isEmpty()) {
    try {
        for (ChannelSftp.LsEntry file : files) {
            String fileName = file.getFilename();
            String newName = stamp + "_" + fileName;
            final File tempFile = File.createTempFile("stream2file", "tmp");
            // 将InputStream和OutputStream都纳入try-with-resources,自动关闭资源
            try (InputStream inputStream = sftpChannel.get(fileName);
                 FileOutputStream outputStream = new FileOutputStream(tempFile)) {
                IOUtils.copy(inputStream, outputStream);
                s3bucket.putObject(new PutObjectRequest("TEST", newName, tempFile));
                // 上传完成后主动删除临时文件
                if (!tempFile.delete()) {
                    LOGGER.log(Level.WARNING, "无法删除临时文件: " + tempFile.getAbsolutePath());
                }
            } catch (IOException e) {
                LOGGER.log(Level.WARNING, "处理文件 " + fileName + " 出错: ", e);
                // 异常时也要尝试删除临时文件,避免磁盘泄漏
                if (tempFile.exists() && !tempFile.delete()) {
                    LOGGER.log(Level.WARNING, "异常时无法删除临时文件: " + tempFile.getAbsolutePath());
                }
            }
        }
    } catch (FileNotFoundException e) {
        LOGGER.log(Level.INFO, "未找到目标文件");
    } catch (IOException e) {
        LOGGER.log(Level.INFO, "IO操作异常");
    } finally {
        // 先判断连接状态再断开,避免重复操作引发异常
        if (channel != null && channel.isConnected()) {
            channel.disconnect();
            LOGGER.log(Level.INFO, "SFTP通道已断开");
        }
        if (session != null && session.isConnected()) {
            session.disconnect();
            LOGGER.log(Level.INFO, "SFTP会话已断开");
        }
    }
}

额外优化建议

  • 考虑使用S3的流式上传能力,无需生成临时文件,彻底避免磁盘占用问题,同时减少内存开销:
    // 直接上传InputStream到S3,跳过临时文件步骤
    s3bucket.putObject(new PutObjectRequest("TEST", newName, inputStream, new ObjectMetadata())
            .withCannedAcl(CannedAccessControlList.Private));
    
  • 对大文件可以考虑分段上传,进一步降低单文件处理的内存占用。

内容的提问来源于stack exchange,提问作者Andre S

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 11:47:49