Why does Java's InflaterInputStream (and other similar classes) only conditionally call end on it's internal Inflater

conorgriffin :

In Java 8 the close() method for InflaterInputStream is as shown below

public void close() throws IOException {
    if (!closed) {
        if (usesDefaultInflater)
            inf.end();
        in.close();
        closed = true;
    }
}

usesDefaultInflater is a boolean that is only true if the constructor below is used

public InflaterInputStream(InputStream in) {
    this(in, new Inflater());
    usesDefaultInflater = true;
}

Any other constructor such as this one below results in this boolean being set to false

new InflaterInputStream(decryptInputStream, new Inflater(), 4096);

As a result, unless you use the default constructor the end() method is not called on the Inflater and this means unnecessary native memory consumption until the finalize method is called on the Inflater by the Finalizer thread potentially a long time after the InflaterInputStream is closed. See the implementation in Inflater below.

/**
 * Closes the decompressor and discards any unprocessed input.
 * This method should be called when the decompressor is no longer
 * being used, but will also be called automatically by the finalize()
 * method. Once this method is called, the behavior of the Inflater
 * object is undefined.
 */
public void end() {
    synchronized (zsRef) {
        long addr = zsRef.address();
        zsRef.clear();
        if (addr != 0) {
            end(addr);
            buf = null;
        }
    }
}

/**
 * Closes the decompressor when garbage is collected.
 */
protected void finalize() {
    end();
}

To get around this you need to override the close method on the InflaterInputStream like so

new InflaterInputStream(decryptInputStream, new Inflater(), 4096) {    
    @Override
    public void close() throws IOException {
        try {
            super.close();
        } finally {
            inf.end();
        }
    }
}

This is easily missed and it seems to me like it might have been sensible to call end() by default and allow the user to override that behaviour by providing a constructor where you could specify false, or at the very least a constructor that uses the default Inflater but that also allows you to set the buffer size.

Anyway, I'm guessing there's some logical reason that it's designed the way it is and I've just failed to grok it. Hoping someone can enlighten me...

This also applies to DeflaterInputStream, DeflaterOutputStream, and InflaterOutputStream amongst others.

Andreas :

There are many methods in the Java Runtime Library that takes e.g. an OutputStream (such as Files.copy()). Unless those methods explicitly state that the stream will be closed by the method, the stream will not be closed. Closing the stream is the responsibility of the stream "owner", e.g. the caller of the method.

Similarly, neither constructor of InflaterInputStream that takes an Inflater states that they will end() the Inflater, which means that they won't. It is up to the caller to end it, when needed.

When using the constructor that creates the Inflater for you, the InflaterInputStream becomes the "owner" of that internal Inflater, and it is therefore the responsibility of the InflaterInputStream to end the Inflater.

Resource Management

The general guideline for resource management is that, unless otherwise documented, whoever allocates a resource is responsible for releasing (closing, ending, ...) the resource.

Inflater is a resource, so normal resource management rules apply.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=138789&siteId=1