diff --git a/src/com/sun/jna/internal/Cleaner.java b/src/com/sun/jna/internal/Cleaner.java index a2095937f..1fe5ac657 100644 --- a/src/com/sun/jna/internal/Cleaner.java +++ b/src/com/sun/jna/internal/Cleaner.java @@ -27,6 +27,12 @@ import java.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; import java.util.logging.Logger; @@ -44,65 +50,79 @@ public static Cleaner getCleaner() { return INSTANCE; } + // Guard for trackedObjects and cleanerThread. The readlock is utilized when + // the trackedObjects are manipulated, the writelock protectes starting and + // stopping the CleanerThread + private final ReadWriteLock cleanerThreadLock = new ReentrantReadWriteLock(); private final ReferenceQueue referenceQueue; - private Thread cleanerThread; - private CleanerRef firstCleanable; + // Count of objects tracked by the cleaner. When >0 it means objects are + // being tracked by the cleaner and the cleanerThread must be running + private final AtomicLong trackedObjects = new AtomicLong(); + // Map only serves as holder, so that the CleanerRefs stay hard referenced + // and quickly accessible for removal + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") + private final Map map = new ConcurrentHashMap<>(); + // Thread to handle the actual cleaning + private volatile Thread cleanerThread; private Cleaner() { referenceQueue = new ReferenceQueue<>(); } - public synchronized Cleanable register(Object obj, Runnable cleanupTask) { + @SuppressWarnings("EmptySynchronizedStatement") + public Cleanable register(Object obj, Runnable cleanupTask) { // The important side effect is the PhantomReference, that is yielded // after the referent is GCed - return add(new CleanerRef(this, obj, referenceQueue, cleanupTask)); + try { + return add(new CleanerRef(this, obj, referenceQueue, cleanupTask)); + } finally { + synchronized (obj) { + // Used as a reachability fence for obj + // Ensure, that add completes before obj can be collected and + // the cleaner is run + } + } } - private synchronized CleanerRef add(CleanerRef ref) { - synchronized (referenceQueue) { - if (firstCleanable == null) { - firstCleanable = ref; - } else { - ref.setNext(firstCleanable); - firstCleanable.setPrevious(ref); - firstCleanable = ref; - } - if (cleanerThread == null) { - Logger.getLogger(Cleaner.class.getName()).log(Level.FINE, "Starting CleanerThread"); - cleanerThread = new CleanerThread(); - cleanerThread.start(); + private CleanerRef add(CleanerRef ref) { + map.put(ref, ref); + cleanerThreadLock.readLock().lock(); + try { + long count = trackedObjects.incrementAndGet(); + if (cleanerThread == null && count > 0) { + cleanerThreadLock.readLock().unlock(); + cleanerThreadLock.writeLock().lock(); + try { + if (cleanerThread == null && trackedObjects.get() > 0) { + Logger.getLogger(Cleaner.class.getName()).log(Level.FINE, "Starting CleanerThread"); + cleanerThread = new CleanerThread(); + cleanerThread.start(); + } + } finally { + cleanerThreadLock.readLock().lock(); + cleanerThreadLock.writeLock().unlock(); + } } - return ref; + } finally { + cleanerThreadLock.readLock().unlock(); } + return ref; } - private synchronized boolean remove(CleanerRef ref) { - synchronized (referenceQueue) { - boolean inChain = false; - if (ref == firstCleanable) { - firstCleanable = ref.getNext(); - inChain = true; - } - if (ref.getPrevious() != null) { - ref.getPrevious().setNext(ref.getNext()); - } - if (ref.getNext() != null) { - ref.getNext().setPrevious(ref.getPrevious()); - } - if (ref.getPrevious() != null || ref.getNext() != null) { - inChain = true; - } - ref.setNext(null); - ref.setPrevious(null); - return inChain; + private void remove(CleanerRef ref) { + map.remove(ref); + cleanerThreadLock.readLock().lock(); + try { + trackedObjects.decrementAndGet(); + } finally { + cleanerThreadLock.readLock().unlock(); } } private static class CleanerRef extends PhantomReference implements Cleanable { private final Cleaner cleaner; private final Runnable cleanupTask; - private CleanerRef previous; - private CleanerRef next; + private final AtomicBoolean cleaned = new AtomicBoolean(false); public CleanerRef(Cleaner cleaner, Object referent, ReferenceQueue q, Runnable cleanupTask) { super(referent, q); @@ -112,26 +132,11 @@ public CleanerRef(Cleaner cleaner, Object referent, ReferenceQueue