package nb; import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; /** * Created from {@link sun.misc.Cleaner} and adapted to use it's own singleton thread * instead of the {@link java.lang.ref.Reference.ReferenceHandler}... */ public class Cleaner extends PhantomReference { // Doubly-linked list of live cleaners, which prevents the cleaners // themselves from being GC'd before their referents // private static Cleaner first; private Cleaner next, prev; private static synchronized Cleaner add(Cleaner cl) { if (first == null) { startHandler(); } else { cl.next = first; first.prev = cl; } first = cl; return cl; } private static synchronized boolean remove(Cleaner cl) { // If already removed, do nothing if (cl.next == cl) { return false; } // Update list if (first == cl) { if (cl.next != null) { first = cl.next; } else { first = cl.prev; } } if (cl.next != null) { cl.next.prev = cl.prev; } if (cl.prev != null) { cl.prev.next = cl.next; } // Indicate removal by pointing the cleaner to itself cl.next = cl; cl.prev = cl; if (first == null) { stopHandler(); } return true; } private static final ReferenceQueue queue = new ReferenceQueue<>(); private static class CleanerHandler extends Thread { volatile boolean stop; CleanerHandler() { super("CleanerHandler"); //setDaemon(true); } void shutdown() { stop = true; if (Thread.currentThread() != this) { interrupt(); } } @Override public void run() { while (!stop) { try { ((Cleaner) queue.remove()).clean(); } catch (InterruptedException ignore) {} } System.out.println("Stopping: " + this); } } private static CleanerHandler handler; private static void startHandler() { assert handler == null; handler = new CleanerHandler(); System.out.println("Starting: " + handler); handler.start(); } private static void stopHandler() { assert handler != null; handler.shutdown(); handler = null; } private final Runnable thunk; private Cleaner(Object referent, Runnable thunk) { super(referent, queue); this.thunk = thunk; } /** * Creates a new cleaner. * * @param ob the referent object to be cleaned * @param thunk The cleanup code to be run when the cleaner is invoked. The * cleanup code is run from special internal thread. * @return The new cleaner */ public static Cleaner create(Object ob, Runnable thunk) { if (thunk == null) return null; return add(new Cleaner(ob, thunk)); } /** * Runs this cleaner, if it has not been run before. */ public void clean() { if (!remove(this)) return; try { thunk.run(); } catch (Throwable t) { System.err.println("Cleaner thunk exception caught: " + t); } } public static void main(String[] args) throws Exception { Object o1 = new Object(); Cleaner.create(o1, () -> System.out.println("Cleaning-up after o1...")); Object o2 = new Object(); Cleaner.create(o2, () -> System.out.println("Cleaning-up after o2...")); o1 = null; System.gc(); Thread.sleep(1000L); o2 = null; System.gc(); Thread.sleep(1000L); Object o3 = new Object(); Cleaner.create(o3, () -> System.out.println("Cleaning-up after o3...")); o3 = null; System.gc(); Thread.sleep(1000L); } }