RFR: 8156484: ZipFile retains too much native memory from caching Inflaters
Peter Levart
peter.levart at gmail.com
Thu May 19 15:17:38 UTC 2016
Hi Martin,
To make it simpler to compare your and mine changes, here's the diff
between your changed ZipFile.java and mine (mostly just removal of code):
diff -r 45dcd8ef14a7 src/java.base/share/classes/java/util/zip/ZipFile.java
--- a/src/java.base/share/classes/java/util/zip/ZipFile.java Thu May
19 17:10:12 2016 +0200
+++ b/src/java.base/share/classes/java/util/zip/ZipFile.java Thu May
19 17:12:54 2016 +0200
@@ -31,11 +31,9 @@
import java.io.EOFException;
import java.io.File;
import java.io.RandomAccessFile;
-import java.lang.ref.WeakReference;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.Path;
import java.nio.file.Files;
import java.util.ArrayList;
@@ -43,21 +41,18 @@
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.Map;
import java.util.Objects;
import java.util.NoSuchElementException;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import jdk.internal.misc.JavaUtilZipFileAccess;
import jdk.internal.misc.SharedSecrets;
import jdk.internal.perf.PerfCounter;
-import static java.util.zip.ZipConstants.*;
import static java.util.zip.ZipConstants64.*;
import static java.util.zip.ZipUtils.*;
@@ -370,7 +365,7 @@
private boolean eof = false;
ZipFileInflaterInputStream(ZipFileInputStream zfin, int size) {
- super(zfin, getInflater(), size);
+ super(zfin, new Inflater(true), size, true);
}
public void close() throws IOException {
@@ -379,7 +374,6 @@
synchronized (streams) {
streams.remove(this);
}
- releaseInflater(inf);
}
}
@@ -408,72 +402,6 @@
? Integer.MAX_VALUE
: (int) avail;
}
-
- protected void finalize() throws Throwable {
- close();
- }
- }
-
- /**
- * A "pooled" inflater. The weak reference does not prevent
finalization
- * of the Inflater, but if get() returns non-null, then the referent is
- * surely not yet eligible for finalization (which would otherwise be a
- * rich source of bugs).
- */
- static class CachedInflater extends WeakReference<Inflater> {
- final AtomicBoolean inUse;
- CachedInflater(Inflater inf, boolean inUse) {
- super(inf);
- this.inUse = new AtomicBoolean(inUse);
- }
- }
-
- /**
- * Cache of Inflaters. We use a simple one-element "pool".
- * Performance measurements show that it's barely profitable to
- * cache an Inflater (which consumes around 32kb of native memory)
- * while iterating through a zip file and decompressing many small
- * entries.
- */
- private static final AtomicReference<CachedInflater> inflaterCache
- = new AtomicReference<>(new CachedInflater(new Inflater(true),
false));
-
- /**
- * Returns an Inflater, either a new one, or cached and reset.
- */
- private static Inflater getInflater() {
- CachedInflater cachedInflater = inflaterCache.get();
- Inflater inf = cachedInflater.get();
- if (inf == null) {
- inf = new Inflater(true);
- // attempt to install as the new cache, but failure is OK.
- inflaterCache.compareAndSet(cachedInflater,
- new CachedInflater(inf, true));
- return inf;
- } else if (!cachedInflater.inUse.get()
- && cachedInflater.inUse.compareAndSet(false, true)) {
- assert !inf.ended();
- // we now "own" this Inflater, but must keep it weakly
referenced.
- return inf;
- } else {
- return new Inflater(true);
- }
- }
-
- /**
- * Releases the Inflater for possible later reuse.
- */
- private static void releaseInflater(Inflater inf) {
- assert !inf.ended();
- CachedInflater cachedInflater = inflaterCache.get();
- if (inf != cachedInflater.get()) {
- inf.end();
- } else {
- // "return" the Inflater to the "pool".
- inf.reset();
- assert cachedInflater.inUse.get();
- cachedInflater.inUse.set(false);
- }
}
/**
@@ -804,10 +732,6 @@
}
}
}
-
- protected void finalize() {
- close();
- }
}
static {
Regards, Peter
On 05/19/2016 05:00 PM, Martin Buchholz wrote:
> On Thu, May 19, 2016 at 7:29 AM, Peter Levart <peter.levart at gmail.com> wrote:
>> But I have reservation for the implementation of one-element global cache of
>> Inflater. This cache can't be efficient. In order for cache to be efficient,
>> majority of calls to ZipFile.getInputStream(zipEntry) would have to be
>> accompanied by a corresponding explicit close() for the input stream before
>> the WeakReference to the cached Inflater is cleared.
> That's my assumption. In most cases, failure to close something that
> can be closed is a bug.
> If there's code in the JDK that fails to do that, it should be fixed
> independently.
>
>> The "assert !inf.ended()" in
>> releaseInflater() can therefore fail as final() methods on individual
>> objects that are eligible for finalization may be invoked in arbitrary
>> order.
> Yeah, that's a bug. We can only assert after we verify that the
> Inflater is still weakly reachable.
> Updating my webrev with:
>
> * Releases the Inflater for possible later reuse.
> */
> private static void releaseInflater(Inflater inf) {
> - assert !inf.ended();
> CachedInflater cachedInflater = inflaterCache.get();
> if (inf != cachedInflater.get()) {
> inf.end();
> } else {
> // "return" the Inflater to the "pool".
> + assert !inf.ended();
> inf.reset();
> assert cachedInflater.inUse.get();
> cachedInflater.inUse.set(false);
More information about the core-libs-dev
mailing list