I have a class
class Something {
private Map<String, RandomAccessFile> map = new LinkedHashMap<>() {
@Override
protected boolean removeEldestEntry(...) { also closes the file if it returns true; }
@Override
public RandomAccessFile remove(Object filename) { closes the file too; }
};
}
This is a map of filename to the file handle. The idea is to keep a few files open, so that member functions of the Something class can write to any of the open files.
Now I'd like to override finalize so that when the object goes out of scope, we close all files. But finalize is deprecated. So the idea is to call cleaner.register(map, runnable) where runnable gets all the RandomAccessFile in 'map' and closes each. I.e. something like cleaner.register(map, () -> map.values().forEach(close file))
. But this makes runnable have a reference to the map, so the map can never become unreachable - class LinkedHashMap.LinkedValues is not a static class.
Any ideas how to do accomplish this?
You have to separate the object whose reachability should control the cleanup and the data needed for the cleanup.
In your case, the lifetime of the
Something
instance would determine whether the files are still in use, whereas the map instance can be used during the cleanup action, assuming that you keep the map private and never hand it out.There are still some important points to care for. The documentation of
Cleaner
says:When you use the lambda expression
() -> map.values().forEach(close file))
andmap
is a field access of the currentSomething
instance, you have implicitly capturedthis
. But even if you fix the cleaner, to let it only reference the map instance, there’s the problem that the map itself is an anonymous subclass ofLinkedHashMap
and anonymous inner classes always have an implicit reference to their outer instance.You can fix both by performing the initialization in a static context:
By using a
static
method, there can’t be any implicit reference tothis
and the explicit reference provided in theinstance
parameter has been deliberately typed asObject
, to make it harder to accidentally accessing a member in the map class or cleanup action.But note that the
LinkedHashMap
does not guaranty that all removal operations go through that single overriddenremove
method. Further, replacement operations do not useremove
. It may work for your internal use when thatremove
is the only operation (besidesput
) theSomething
class uses, but in this case, it would also be possible and cleaner to make file closing the caller’s duty.That said, you should not implement the cleaner at all. If all the cleanup is doing, is to close the
RandomAccessFile
, it’s obsolete as there is already a cleaner closing the underlying resource whenRandomAccessFile
becomes unreachable without being closed. If relying on that cleaner feels hacky, you’re on the right track. But relying onSomething
’s cleaner is in no way better.Generally, variable scopes and object reachability are only remotely connected. See Can java finalize an object when it is still in scope? or finalize() called on strongly reachable objects in Java 8 for a connected real life problem. But of course, the timing issues described in When is the finalize() method called in Java? apply to cleaners as well. The garbage collector may never run, e.g. when there’s enough memory, or it may run but not collect all unreachable objects, e.g. when it reclaimed enough memory for the application to proceed.
When you want to clean up at the end of a variable’s scope, the try-with-resources Statement is the way to go. You only have to implement
AutoCloseable
or a subtype of it, providing aclose()
method that will close all files.Then you can easily say