RFR JDK-8185582, Update Zip implementation to use Cleaner, not finalizers

Peter Levart peter.levart at gmail.com
Fri Nov 3 08:48:25 UTC 2017

Hi Sherman,

I think this looks good now. Thanks.

Regards, Peter

On 11/01/2017 08:17 PM, Xueming Shen wrote:
> 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