RFR: 8156484: ZipFile retains too much native memory from caching Inflaters
Peter Levart
peter.levart at gmail.com
Thu May 19 14:29:00 UTC 2016
Hi Martin,
On 05/19/2016 04:27 AM, Martin Buchholz wrote:
> Another batch of ZipFile hackery. I think I finally understand how
> weak references and finalizers interact.
> We could eliminate the Inflater cache entirely, but this proposal
> keeps a one-element Inflater cache.
>
> http://cr.openjdk.java.net/~martin/webrevs/openjdk9/ZipFile-Inflater-cache/
> https://bugs.openjdk.java.net/browse/JDK-8156484
>
> - fixes the memory bloat from cached Inflaters
> - removes small races in close
> - various other minor improvements
The fixes to races are (almost) good. Also other minor improvements and
cleanups.
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.
We know that all WeakReference(s) to a referent are 1st cleared and then
the finalize() method is called on it. GC clears all WeakReference(s) to
interconnected graph of weakly-reachable referents atomically, so if the
ZipFileInflaterInputStream is closed implicitly by the finalize()
invocation which then tries to releaseInflater(), the inflater will not
be released as the 'cachedInflater' WeakReference will already be
cleared. It is too late to release inflater into cache anyway because if
the input stream is already being finalized, then the associated
inflater has either already been finalized or will be shortly as GC
discovers and enqueues the FinalReference(s) pointing to inflater and
corresponding input stream in the same GC cycle - Inflater and
corresponding input stream become eligible for finalization at the same
time. 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.
To correct that and improve the efficiency of cache, we have to do two
things:
- we can't fix all the usages of ZipFile.getInputStream(zipEntry) out
there, but we can at least fix the usages in URLClassLoader and
BuiltinClassLoader which amount to most getInputStream() invocations
without corresponding close()s when classes are being loaded (one stream
for each class).
- we can make the Inflater cache a bit smarter.
http://cr.openjdk.java.net/~plevart/jdk9-dev/ZipFile_Inflater.cleanup/webrev.01/
We don't actually need to cache the Inflater objects themselves, but
rather Inflater's internal ZStreamRef(s) which are just objects holding
address to native zlib's z_stream structure. I also suggest using new
Cleaner API for tracking reachability of Inflater(s) so that
Inflater.end() that results from explicit stream close() clears the
PhantomCleanable and allows GC to refrain from pushing already end()-ed
Inflater(s)/FinalReference(s) through the finalization pipeline. I also
took the liberty to create a trusted package-private InflaterInputStream
constructor so that trusted subclasses can use it and so that
usesDefaultInflater flag can be private and final. Deflater is using
ZStreamRef(s) too, so I also had to modify Deflater, but there's no
caching of ZStreamRef(s) in deflater as there are too many different
kinds of them and this is not critical.
By making ZStreamRef(s) take care of themselves, corresponding logic in
ZipFile's streams is much simpler - no need for finalize() method any
more on ZipFileInflaterInputStream. finalize() is also not needed on
ZipFileInputStream any more as 'streams' WeakHashMap is not holding
Inflater instances as values any more and so finalize() would serve only
to remove finalized ZipFile[Inflater]InputStream(s) from the WeakHashMap
which the map is doing by itself already (expunging).
java.util.zip tests pass with this patch except 2 tests that are ignored
and ZipFile/TestZipFile.java that prints:
java.lang.OutOfMemoryError: Java heap space: failed reallocation of
scalar replaced objects
...which seems unrelated and the test passes when executed alone.
This touches quite a number of other classes, but it seems necessary.
So Martin, what do you think?
Regards, Peter
More information about the core-libs-dev
mailing list