package org.netbeans.gradle.model.util; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; /** * Defines a simple safety-net for objects managing unmanaged resources (e.g.: * files). * <P> * {@code ObjectFinalizer} takes a {@link Runnable} in its constructor and * allows it to be called in the {@link #doFinalize() doFinalize()} method. * Also, {@code ObjectFinalizer} declares a {@link Object#finalize() finalizer}, * in which it will call {@code run()} method of the specified {@code Runnable} * if it was not called manually (by calling {@code doFinalize()}). * <P> * Once the {@code doFinalize()} method of an {@code ObjectFinalizer} was called * it will no longer retain a reference to the {@code Runnable} specified at * construction time. * <P> * See the following example code using {@code ObjectFinalizer}: * <code><pre> * class UnmanagedResourceHolder implements Closable { * privata final ObjectFinalizer finalizer; * * // Other declarations ... * * public UnmanagedResourceHolder() { * // Initialization code ... * * this.finalizer = new ObjectFinalizer(new Runnable() { * public void run() { * doCleanup(); * } * }, "UnmanagedResourceHolder.cleanup"); * } * * // Other code ... * * private void doCleanup() { * // cleanup unmanaged resources * } * * {@literal @Override} * public void close() { * finalizer.doFinalize(); * } * } * </pre></code> * <P> * Assume, that in the above code an {@code UnmanagedResourceHolder} instance * becomes unreachable and as such is eligible for garbage collection. Notice * that in this case {@code finalizer} also becomes unreachable and such also * eligible for garbage collection. Also assume that the {@code close()} method * was not called. In this case when the JVM decides to cleanup the now * unreachable {@code finalizer} instance, it will call its finalizer and in * turn invoke the {@code doCleanup()} method releasing the unmanaged resources. * * <h3>Unmanaged Resources</h3> * In Java a garbage collector is employed, so the programmer is relieved from * the burden (mostly) of manual memory management. However, the garbage * collector cannot handle anything beyond the memory allocated for objects, so * other unmanaged resources require a cleanup method. Although there are * {@link Object#finalize() finalizers} in Java, they are unreliable and the * only correct solution is the use of a cleanup method. Generally, objects * managing unmanaged resources should implement the {@link AutoCloseable} * interface. * <P> * Notice however, that a bug may prevent the program to call the cleanup * method of an object, causing the leakage of unmanaged resource, possibly * leading to resource exhaustion. In this case finalizers can be useful to * provide a safety-net, that even in the case of such previously mentioned bug, * a finalizer can be implemented to do the cleanup, so when the JVM actually * removes the object, it may cleanup the unmanaged resources. * * <h3>Benefits of {@code ObjectFinalizer}</h3> * One may ask what are the benefits of using {@code ObjectFinalizer} instead of * directly declaring a finalizer. There are actually three main benefits of * using {@code ObjectFinalizer}: * <P> * There is a penalty for declaring a {@code finalize()} method in a class: Such * objects are slower to be created and will be garbage collected later. There * is nothing to be done about slower object creation but the use of * {@code ObjectFinalizer} will help with the slower garbage collection. * It is assumed that the common case is that the {@code doFinalize()} method is * called from the code correctly without relying on the object finalizer * (ideally this should always be the case). In this case once the * {@code doFinalize()} method was called, the {@code ObjectFinalizer} will * store no reference to the object it cleaned up, allowing this object to be * garbage collected. Note that the {@code ObjectFinalizer} instance will still * suffer from the slower garbage collection but an {@code ObjectFinalizer} * whose {@code doFinalize()} method was called is relatively cheap in terms of * retained memory. So this can be a considerable benefit if the object * protected by the {@code ObjectFinalizer} retains considerable amount of * memory. * <P> * Notice that the {@code run()} method of the specified {@code Runnable} * instance can only be called at most once even if the {@code doFinalize()} * method is called concurrently multiple times. This effectively makes the * cleanup method idempotent for free which makes the cleanup method safer. * <P> * In case the cleanup method was failed to be called, the * {@code ObjectFinalizer} will log this failure when it detects in its * finalizer, that the {@code doFinalize()} method was not called. So this error * will be documented in the logs and can be analyzed later. * * <h3>Thread safety</h3> * This class is safe to be used by multiple threads concurrently. * * <h4>Synchronization transparency</h4> * Unless otherwise noted, methods of this class are not * <I>synchronization transparent</I>. */ public final class ObjectFinalizer { private static final String MISSED_FINALIZE_MESSAGE = "An object was not finalized explicitly." + " Finalizer task: {0}/{1}."; private static final Logger LOGGER = Logger.getLogger(ObjectFinalizer.class.getName()); private final AtomicReference<Runnable> finalizerTask; private final String className; private final String taskDescription; /** * Creates a new {@code ObjectFinalizer} using the specified * {@code Runnable} to be called by the {@link #doFinalize() doFinalize()} * method. * <P> * The task description to be used in the log when {@code doFinalize()} * is failed to get called is the result of the {@code toString()} * method of the specified task. The result of the {@code toString()} is * retrieved in this constructor call and not when actually required. * * @param finalizerTask the task to be invoked by the {@code doFinalize()} * method to cleanup unmanaged resources. This argument cannot be * {@code null}. * * @throws NullPointerException thrown if the specified task is * {@code null} or its {@code toString()} method returns {@code null} */ public ObjectFinalizer(Runnable finalizerTask) { this(finalizerTask, finalizerTask.toString()); } /** * Creates a new {@code ObjectFinalizer} using the specified * {@code Runnable} to be called by the {@link #doFinalize() doFinalize()} * method and a task description to be used in logs if {@code doFinalize()} * is only called in the finalizer. * * @param finalizerTask the task to be invoked by the {@code doFinalize()} * method to cleanup unmanaged resources. This argument cannot be * {@code null}. * @param taskDescription the description to be added to the log message * if the {@code doFinalize()} only gets called in the finalizer * * @throws NullPointerException thrown if the specified task or the task * description is {@code null} */ public ObjectFinalizer(Runnable finalizerTask, String taskDescription) { if (finalizerTask == null) throw new NullPointerException("finalizerTask"); if (taskDescription == null) throw new NullPointerException("taskDescription"); this.finalizerTask = new AtomicReference<Runnable>(finalizerTask); this.taskDescription = taskDescription; this.className = finalizerTask.getClass().getName(); } /** * Sets the state as if {@link #doFinalize() doFinalize()} has been called * but does not actually call {@code doFinalize}. This method is useful if * the object has been finalized in another way, so it is no longer an error * not to finalize the object. * <P> * After calling this method, subsequent {@code doFinalize()} method calls * will do nothing. */ public void markFinalized() { finalizerTask.set(null); } /** * Invokes the task specified at construction time if it was not called yet. * The task will be called only once, even if this method is called * concurrently by multiple threads. * <P> * The task is invoked synchronously on the current calling thread. Note * that therefore, this method will propagate every exception to the caller * thrown by the task. * <P> * Once this method returns (even if the called task throws an exception), * the task specified at construction time will no longer be referenced by * this {@code ObjectFinalizer}. * * @return {@code true} if this method actually invoked the task specified * at construction time, {@code false} if {@code doFinalize()} was already * called (or another {@code doFinalize()} is executing the task * concurrently) */ public boolean doFinalize() { Runnable task = finalizerTask.getAndSet(null); if (task != null) { task.run(); return true; } return false; } /** * Returns {@code true} if {@link #doFinalize() doFinalize()} has already * been called. In case this method returns {@code true}, subsequent calls * to {@code doFinalize()} will do nothing but return immediately to the * caller. * * @return {@code true} if {@code doFinalize()} has already * been called, {@code false} otherwise */ public boolean isFinalized() { return finalizerTask.get() == null; } /** * Throws an {@link IllegalStateException} if the * {@link #doFinalize() doFinalize()} method has already been called. This * method can be used to implement a fail-fast behaviour when the object * this {@code ObjectFinalizer} protects is being used after cleanup. * * @throws IllegalStateException thrown if {@code doFinalize()} has already * been called. That is, if {@link #isFinalized() isFinalized()} returns * {@code true}. */ public void checkNotFinalized() { if (isFinalized()) { throw new IllegalStateException("Object was already finalized: " + className + "/" + taskDescription); } } /** * Invokes the task specified at construction time if it has not been called * yet. In case the task has already been called, this method does nothing * but returns immediately. * <P> * If the task has not been called previously (which is considered to be an * error), this method will log a {@link Level#SEVERE SEVERE} level log * message using the {@code java.util.logging} facility. The logging will be * done even if the task throws an exception in which case this exception * will also be attached to the log message. */ @Override @SuppressWarnings("FinalizeDoesntCallSuperFinalize") protected void finalize() { Throwable exception = null; Runnable task = null; try { task = finalizerTask.getAndSet(null); if (task != null) { task.run(); } } catch (Throwable ex) { exception = ex; } if (task != null && LOGGER.isLoggable(Level.SEVERE)) { LogRecord logRecord = new LogRecord(Level.SEVERE, MISSED_FINALIZE_MESSAGE); logRecord.setSourceClassName(ObjectFinalizer.class.getName()); logRecord.setSourceMethodName("finalize()"); logRecord.setThrown(exception); logRecord.setParameters(new Object[]{className, taskDescription}); LOGGER.log(logRecord); } } }