【问题标题】:Java Outputstream behavior when multiple outputstream objects are wrapped包装多个输出流对象时的 Java 输出流行为
【发布时间】:2009-12-04 09:01:37
【问题描述】:

我有一个对文件输出流进行压缩、加密和校验和的代码。以下是代码-

private void start() {
    OutputStream os = null;
    try {
        os = new FileOutputStream("/some/file");
        os = wrapAllRequiredTransforms(os);

        //Write to os
    } finally {
        os.close();
    }
}

private wrapAllRequiredTransforms(OutputStream os) {
    if(checkSumRequired) {
        os = wrapOStreamWithCheckSum(os);
    }

    if(encryptionRequired) {
        os = wrapOStreamWithCipher(os);
    }

    if(compressRequired) {
        os = wrapOStreamWithCompress(os);
    }
}

private OutputStream wrapOStreamWithCheckSum(OutputStream os) throws Exception {
    os = new DigestOutputStream(os, MessageDigest.getInstance("MD5"));
    return os;
}

private OutputStream wrapOStreamWithCipher(OutputStream os) throws Exception {
    SecretKeySpec secretKeySpec = new SecretKeySpec(//SomeKey, encryptionAlgorithm);
    Cipher cipher = Cipher.getInstance(encryptionAlgorithm); 
    cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
    return new CipherOutputStream(os, cipher);
}

private OutputStream wrapOStreamWithCompress(OutputStream os) throws Exception {
    return new GZIPOutputStream(os);
}

正如您在此处看到的,我正在包装“os”对象以进行加密、压缩等,然后在每个 wrapOStreamWithCheckSum、wrapOStreamWithCipher 和 wrapOStreamWithCompress 方法中重新分配具有不同对象(使用 new 创建)的“os”变量.我想知道这是否会导致内存泄漏?创建的较旧的“os”对象实际上会发生什么?换句话说,使用“new”创建了 4 个对象,但被重新分配给相同的“os”变量。我发现很难理解,因为新对象的创建/运行本身依赖于内部的旧对象。

【问题讨论】:

  • (您的 try/finally 已损坏。如果找不到文件会发生什么情况。请使用 acquire(); try { use(); } finally { release(); }。在这种情况下,类似于:final OutputStream rawOut = new FileOutputStream("/some/file/"); try { final OutputStream out = wrap(rawOut); ... } finally { rawOut.close(); }
  • 谢谢汤姆!是的,你是对的,这里没有处理错误情况(事实上,上面的代码 sn-p 甚至在复制到程序时都不会编译。)上面的代码 sn-p 试图仅说明我试图理解的问题。 :)

标签: java memory-leaks garbage-collection object outputstream


【解决方案1】:

只有当一个对象可以通过堆栈引用并且您不再希望它在内存中时,才会发生内存泄漏。

一个例子是这样的:

public class Main
{
    private static CommandLineArgumentParser parser;

    public static void main(final String[] argv)
    {
        parser = new CommandLineArgumentParser(argv);
        ... use the parser
        ... never use the parser again ....
        ... do a bunch of work ...
    }
}

解析器不再使用,但仍可访问,因此从技术上讲,它是内存泄漏(您不再希望使用的内存,但垃圾收集器尚无法回收)。

要使其不再使用,您只需将其设置为 null 或重新分配它,然后就可以收集内存。

在包装的情况下,一旦“根”对象消失,并且只要没有其他活动引用,所有包装对象都将有资格进行垃圾回收。所以,一旦启动方法返回所有在其中创建的对象,就应该能够被收集。

【讨论】:

    【解决方案2】:

    这是 Java 中的标准做法并且安全。

    发生的情况是每个新对象在内部都保留对传入对象的引用。这个过程称为“包装”或“委托”。当您关闭最后一个os 时,它会将方法调用传递给包装的实例。

    这样,一个调用将关闭它们,释放最外层的os 将它们全部释放。

    【讨论】:

      【解决方案3】:

      对您的输出流的所有引用都只是局部变量。因此,在 start() 终止后,不再有对剩余流的引用,“Big GC”将被清理。

      如果您真的想确定并且手头有一个实际的 Eclipse SDK 和实际的 Java (6+),您可以在 os.close() 行中添加一个断点并检查是否有一些意外对象包含对您的引用流。

      【讨论】:

        【解决方案4】:

        Java 具有自动垃圾回收机制,因此您不必担心不释放旧对象,否则会在 C++ 中造成内存泄漏。基本上,您只需确保没有对您不再指向使用变量 os 的对象的悬空引用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-07-22
          • 1970-01-01
          • 2019-10-03
          • 2015-07-27
          • 2017-06-09
          • 1970-01-01
          • 1970-01-01
          • 2017-05-19
          相关资源
          最近更新 更多