RFR JDK-8185582, Update Zip implementation to use Cleaner, not finalizers
Xueming Shen
xueming.shen at oracle.com
Wed Nov 1 19:17:39 UTC 2017
Hi Peter,
I like the idea of moving get/releaseInflter() into CleanableResource,
though I doubt
how much it can really help the GC it should be a good thing to do to
remove the strong
reference of ZipFile from stream's cleaner, and the code appears a
little cleaner as well.
I was debating with myself whether or not the ZipFile.close() should
throw an
UncheckedIOException (like those java.nio.file.Files methods do). But
you're right it's
not good to simply ignoring them silently. Now I catch/unwarp the potential
UncheckedIOException from res.clean() and re-throw the embedded ioe.
I need to dig a little to recall the real reason of zsrc==null check in
ensureOpen()
the comment does not sound updated. res.zsrc is not final and it is set
to null
when clean up/close. So I keep it for now.
Most (if not all?) "minor nit" has been updated accordingly.
It seems like we might have have put the "finalize()" method back as an
empty-body
method for compatibility reason. So not done yet :-)
http://cr.openjdk.java.net/~sherman/8185582/webrev/
thanks,
sherman
On 11/1/17, 5:04 AM, Peter Levart wrote:
> Hi Sherman,
>
> On 11/01/17 00:25, Xueming Shen wrote:
>> Hi Peter,
>>
>> After tried couple implementations it seems the most reasonable
>> approach is to
>> use the coding pattern you suggested to move all pieces into ZSStream
>> Ref. Given
>> we are already using the internal API CleanerFactory it is attractive
>> to go a little
>> further to subclass PhantomCleanable directly (in which I would
>> assume we can
>> save one extra object), but I think I'd better to follow the
>> "suggested" clean usage
>> (to register a runnable into the cleaner) for now.
>>
>> 39 class ZStreamRef implements Runnable {
>> 40
>> 41 private LongConsumer end;
>> 42 private volatile long address;
>> 43 private final Cleanable cleanable;
>> 44
>> 45 ZStreamRef (Object owner, LongSupplier addr, LongConsumer
>> end) {
>> 46 this.cleanable =
>> CleanerFactory.cleaner().register(owner, this);
>> 47 this.end = end;
>> 48 this.address = addr.getAsLong();
>> 49 }
>> 50
>>
>> Similar change has been made for the ZipFile cleaner to follow the
>> same coding
>> pattern. The "cleaner" is now renamed from Releaser to
>> CleanableResource.
>>
>> http://cr.openjdk.java.net/~sherman/8185582/webrev/
>>
>> Thanks,
>> Sherman
>
> This looks mostly fine. One minor nit is that ZStreamRef.address field
> does not have to be volatile. I checked all usages of
> ZStreamRef.address() and all of them invoke it while holding a lock on
> the ZStreamRef instance. The ZStreamRef.run() which modifies the
> address is also synchronized. The other minor nit is that the
> following ZipFile imports are not needed:
>
> import java.nio.file.Path;
> import java.util.Map;
> import jdk.internal.misc.JavaIORandomAccessFileAccess;
> import static java.util.zip.ZipConstants.*;
>
> (at least my IDEA colors them unused)
>
> Cleaner is modified in this webrev (just one empty line deleted) -
> better not touch this file in this changeset
>
> Additional nits in ZipFile:
>
> - in lines 355, 356 you pull out two res fields into locals, but then
> not use them consistently (lines 372, 389)
> - line 403 has a TAB character (is this OK?) and shows incorrectly
> indented in my editor (should I set tab stops differently?)
> - line 457 - while should actually be if ?
> - in ensureOpen() the check for res.zsrc == null can never succeed
> (CleanableResource.zsrc is a final field. If CleanableResource
> constructor fails, there's no res object and there's no ZipFile object
> either as ZipFile constructor does not do anything for this to escape
> prematurely)
> - why don't you let IOExceptions thrown from CleanableResource.run()
> propagate out of ZipFile.close() ?
>
> I would also rename static method Source.close(Source) to
> Source.release(Source) so it would not clash with instance method
> Source.close() which makes it ambiguous when one wants to use
> Source::close method reference (both methods apply). I would also make
> static methods Source.get() and Source.release(Source) package-private
> (currently one is public and the other is private, which needs
> compiler bridges to be invoked) and both are in a private nested class.
>
> Inflater/Deflater/ZipFile now follow the coding pattern as suggested.
> But ZipFileInflaterInputStream still does not. It's not critical since
> failing to register cleanup which releases the inflater back into
> cache would simply mean that Inflater employs its own cleanup and ends
> itself.
>
> And now another thing I would like to discuss. Why an initiative for
> using Cleaner instead of finalization()? Among drawbacks finalization
> has one of the more troubling is that the tracked referent survives
> the GC cycle that initiates its finalization reference processing
> pipeline, so the GC may reclaim the object (and referenced objects)
> only after the finalize() has finished in yet another GC round.
> Cleaner API separates the tracked object from the cleanup function and
> state needed to perform it into distinct instances. The tracked object
> can be reclaimed and the cleanup reference processing pipeline
> initiated in the same GC cycle. More heap may be reclaimed earlier.
>
> Unless we are careful and create a cleaning function for one tracked
> object which captures (directly or indirectly) another object which
> registers its own cleaning function but we don't deal with explicit
> cleaning of the 2nd object in the 1st cleaning function.
>
> Take for example the ZipFileInflaterInputStream's cleaning function.
> It captures the ZipFile in order to invoke ZipFile.releaseInflater
> instance method. What this means is that ZipFile will be kept
> reachable until all ZipFileInflaterInputStream's cleaning functions
> have fired. So we are back to the finalization drawback which needs at
> least 2 GC cycles to collect and clean-up what might be done in one go.
>
> I suggest moving the getInflater() and releaseInflater() from ZipFile
> into the CleanableResource so that ZipFileInflaterInputStream's
> cleaning function captures just the CleanableResource and not the
> ZipFile. ZipFile therefore may become eligible for cleanup as soon as
> all opened input streams become eligible (but their cleaning functions
> need not have fired yet). CleanableResource.run() (the ZipFile
> cleaning function) and CleanableResource.releaseInflater() (the
> ZipFileInflaterInputStream's cleaning function) may therefore be
> invoked in arbitrary order (maybe even concurrently if one of them is
> explicit cleanup and the other is automatic), so code must be prepared
> for that.
>
> I have tried to capture all above in a modified webrev (taking your
> last webrev as a base):
>
> http://cr.openjdk.java.net/~plevart/jdk10-dev/8185582_ZIP.cleaner/webrev.03/
>
>
> Regards, Peter
>
More information about the core-libs-dev
mailing list