1 /*
   2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.internal.ref;
  27 
  28 import jdk.internal.misc.InnocuousThread;
  29 import jdk.internal.misc.JavaLangRefAccess;
  30 import jdk.internal.misc.SharedSecrets;
  31 import jdk.internal.vm.annotation.ReservedStackAccess;
  32 
  33 import java.lang.ref.Cleaner;
  34 import java.lang.ref.ReferenceQueue;
  35 import java.security.AccessController;
  36 import java.security.PrivilegedAction;
  37 import java.util.Objects;
  38 import java.util.concurrent.ThreadFactory;
  39 import java.util.concurrent.atomic.AtomicInteger;
  40 import java.util.concurrent.locks.StampedLock;
  41 import java.util.function.BooleanSupplier;
  42 
  43 /**
  44  * CleanerImpl is the implementation of {@link Cleaner}.
  45  */
  46 public class CleanerImpl implements Cleaner {
  47 
  48     final Task task;
  49 
  50     public CleanerImpl(ThreadFactory threadFactory) {
  51         task = new Task();
  52         task.start(this, threadFactory);
  53     }
  54 
  55     @Override
  56     public Cleanable register(Object obj, Runnable action) {
  57         Objects.requireNonNull(obj, "obj");
  58         Objects.requireNonNull(action, "action");
  59         return new CleanerImpl.PhantomCleanableRef(obj, this, action);
  60     }
  61 
  62     /**
  63      * CleanerImpl.ExtendedImpl is the implementation of {@link ExtendedCleaner}.
  64      */
  65     static class ExtendedImpl extends CleanerImpl implements ExtendedCleaner {
  66 
  67         ExtendedImpl(ThreadFactory threadFactory) {
  68             super(threadFactory);
  69         }
  70 
  71         // A fair lock for threads that retry actions to queue after
  72         // 1st optimistic try fails so that only a single thread at a time is
  73         // retrying actions while helping the Cleaner execute Cleanable(s)
  74         // and trigger new Reference discovery before finally giving up.
  75         private final StampedLock helpingLock = new StampedLock();
  76 
  77         public boolean retryWhileHelpingClean(BooleanSupplier retriableAction) {
  78             // 1st optimistic try - allow concurrent execution of actions
  79             // until helping is necessary
  80             long stamp = helpingLock.tryReadLock();
  81             if (stamp != 0) try {
  82                 if (retriableAction.getAsBoolean()) {
  83                     return true;
  84                 }
  85             } finally {
  86                 helpingLock.unlockRead(stamp);
  87             }
  88 
  89             // retrials with helping are exclusive
  90             stamp = helpingLock.writeLock();
  91             try {
  92                 // retry actions while executing enqueued Cleanable(s) until the
  93                 // queue drains out
  94                 do {
  95                     if (retriableAction.getAsBoolean()) {
  96                         return true;
  97                     }
  98                 } while (task.cleanNextEnqueued());
  99 
 100                 // the queue is now empty but there may have been a long time
 101                 // since last Reference discovery has been performed.
 102                 JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();
 103                 // trigger Reference(s) discovery
 104                 int discoveryPhase = jlra.discoverReferences();
 105                 // wait for newly discovered Reference(s) to be enqueued
 106                 boolean interrupted = false;
 107                 try {
 108                     while (true) {
 109                         try {
 110                             jlra.awaitReferencesEnqueued(discoveryPhase);
 111                             break;
 112                         } catch (InterruptedException e) {
 113                             // ignore interrupts but don't swallow them
 114                             interrupted = true;
 115                         }
 116                     }
 117                 } finally {
 118                     if (interrupted) {
 119                         Thread.currentThread().interrupt();
 120                     }
 121                 }
 122 
 123                 // the queue is now hopefully filled with some freshly discovered
 124                 // Cleanable(s) so retry operation while executing enqueued Cleanable(s)
 125                 // until the queue drains out
 126                 do {
 127                     if (retriableAction.getAsBoolean()) {
 128                         return true;
 129                     }
 130                 } while (task.cleanNextEnqueued());
 131 
 132                 // give up finally
 133                 return false;
 134             } finally {
 135                 helpingLock.unlockWrite(stamp);
 136             }
 137         }
 138     }
 139 
 140     // package-private access to Task's state
 141     PhantomCleanable<?> phantomCleanableList() { return task.phantomCleanableList; }
 142     WeakCleanable<?> weakCleanableList() { return task.weakCleanableList; }
 143     SoftCleanable<?> softCleanableList() { return task.softCleanableList; }
 144     ReferenceQueue<Object> queue() { return task.queue; }
 145 
 146     /**
 147      * CleanerImpl.Task manages a set of object references and corresponding
 148      * cleaning actions and executes them after they are enqueued.
 149      */
 150     private static final class Task implements Runnable {
 151         /**
 152          * Heads of a CleanableList for each reference type.
 153          */
 154         final PhantomCleanable<?> phantomCleanableList;
 155 
 156         final WeakCleanable<?> weakCleanableList;
 157 
 158         final SoftCleanable<?> softCleanableList;
 159 
 160         // The ReferenceQueue of pending cleaning actions
 161         final ReferenceQueue<Object> queue;
 162 
 163         /**
 164          * Constructor for Task.
 165          */
 166         Task() {
 167             queue = new ReferenceQueue<>();
 168             phantomCleanableList = new PhantomCleanableRef();
 169             weakCleanableList = new WeakCleanableRef();
 170             softCleanableList = new SoftCleanableRef();
 171         }
 172 
 173         /**
 174          * Starts the Cleaner implementation.
 175          * Ensure this is the CleanerImpl for the Cleaner.
 176          * When started waits for Cleanables to be queued.
 177          * @param cleaner the cleaner
 178          * @param threadFactory the thread factory
 179          */
 180         void start(CleanerImpl cleaner, ThreadFactory threadFactory) {
 181             if (cleaner.task != this) {
 182                 throw new AssertionError("wrong cleaner");
 183             }
 184             // schedule a nop cleaning action for the cleaner, so the associated thread
 185             // will continue to run at least until the cleaner is reclaimable.
 186             new CleanerCleanable(cleaner);
 187 
 188             if (threadFactory == null) {
 189                 threadFactory = CleanerImpl.InnocuousThreadFactory.factory();
 190             }
 191 
 192             // now that there's at least one cleaning action, for the cleaner,
 193             // we can start the associated thread, which runs until
 194             // all cleaning actions have been run.
 195             Thread thread = threadFactory.newThread(this);
 196             thread.setDaemon(true);
 197             thread.start();
 198         }
 199 
 200         /**
 201          * Process queued Cleanables as long as the cleanable lists are not empty.
 202          * A Cleanable is in one of the lists for each Object and for the Cleaner
 203          * itself.
 204          * Terminates when the Cleaner is no longer reachable and
 205          * has been cleaned and there are no more Cleanable instances
 206          * for which the object is reachable.
 207          * <p>
 208          * If the thread is a ManagedLocalsThread, the threadlocals
 209          * are erased before each cleanup
 210          */
 211         @Override
 212         public void run() {
 213             Thread t = Thread.currentThread();
 214             InnocuousThread mlThread = (t instanceof InnocuousThread)
 215                     ? (InnocuousThread) t
 216                     : null;
 217             while (!phantomCleanableList.isListEmpty() ||
 218                     !weakCleanableList.isListEmpty() ||
 219                     !softCleanableList.isListEmpty()) {
 220                 if (mlThread != null) {
 221                     // Clear the thread locals
 222                     mlThread.eraseThreadLocals();
 223                 }
 224                 try {
 225                     // Wait for a Ref, with a timeout to avoid getting hung
 226                     // due to a race with clear/clean
 227                     Cleanable ref = (Cleanable) queue.remove(60 * 1000L);
 228                     if (ref != null) {
 229                         ref.clean();
 230                     }
 231                 } catch (Throwable e) {
 232                     // ignore exceptions from the cleanup action
 233                     // (including interruption of cleanup thread)
 234                 }
 235             }
 236         }
 237 
 238         /**
 239          * Processes next Cleanable that has been waiting in the queue.
 240          *
 241          * @return {@code true} if a Cleanable was found in the queue and
 242          * was processed or {@code false} if the queue was empty.
 243          */
 244         @ReservedStackAccess
 245         boolean cleanNextEnqueued() {
 246             Cleanable ref = (Cleanable) queue.poll();
 247             if (ref != null) {
 248                 try {
 249                     ref.clean();
 250                 } catch (Throwable t) {
 251                     // ignore exceptions from the cleanup action
 252                 }
 253                 return true;
 254             } else {
 255                 return false;
 256             }
 257         }
 258     }
 259 
 260     /**
 261      * Perform cleaning on an unreachable PhantomReference.
 262      */
 263     public static final class PhantomCleanableRef extends PhantomCleanable<Object> {
 264         private final Runnable action;
 265 
 266         /**
 267          * Constructor for a phantom cleanable reference.
 268          * @param obj the object to monitor
 269          * @param cleaner the cleaner
 270          * @param action the action Runnable
 271          */
 272         public PhantomCleanableRef(Object obj, Cleaner cleaner, Runnable action) {
 273             super(obj, cleaner);
 274             this.action = action;
 275         }
 276 
 277         /**
 278          * Constructor used only for root of phantom cleanable list.
 279          */
 280         PhantomCleanableRef() {
 281             super();
 282             this.action = null;
 283         }
 284 
 285         @Override
 286         protected void performCleanup() {
 287             action.run();
 288         }
 289 
 290         /**
 291          * Prevent access to referent even when it is still alive.
 292          *
 293          * @throws UnsupportedOperationException always
 294          */
 295         @Override
 296         public Object get() {
 297             throw new UnsupportedOperationException("get");
 298         }
 299 
 300         /**
 301          * Direct clearing of the referent is not supported.
 302          *
 303          * @throws UnsupportedOperationException always
 304          */
 305         @Override
 306         public void clear() {
 307             throw new UnsupportedOperationException("clear");
 308         }
 309     }
 310 
 311     /**
 312      * Perform cleaning on an unreachable WeakReference.
 313      */
 314     public static final class WeakCleanableRef extends WeakCleanable<Object> {
 315         private final Runnable action;
 316 
 317         /**
 318          * Constructor for a weak cleanable reference.
 319          * @param obj the object to monitor
 320          * @param cleaner the cleaner
 321          * @param action the action Runnable
 322          */
 323         WeakCleanableRef(Object obj, Cleaner cleaner, Runnable action) {
 324             super(obj, cleaner);
 325             this.action = action;
 326         }
 327 
 328         /**
 329          * Constructor used only for root of weak cleanable list.
 330          */
 331         WeakCleanableRef() {
 332             super();
 333             this.action = null;
 334         }
 335 
 336         @Override
 337         protected void performCleanup() {
 338             action.run();
 339         }
 340 
 341         /**
 342          * Prevent access to referent even when it is still alive.
 343          *
 344          * @throws UnsupportedOperationException always
 345          */
 346         @Override
 347         public Object get() {
 348             throw new UnsupportedOperationException("get");
 349         }
 350 
 351         /**
 352          * Direct clearing of the referent is not supported.
 353          *
 354          * @throws UnsupportedOperationException always
 355          */
 356         @Override
 357         public void clear() {
 358             throw new UnsupportedOperationException("clear");
 359         }
 360     }
 361 
 362     /**
 363      * Perform cleaning on an unreachable SoftReference.
 364      */
 365     public static final class SoftCleanableRef extends SoftCleanable<Object> {
 366         private final Runnable action;
 367 
 368         /**
 369          * Constructor for a soft cleanable reference.
 370          * @param obj the object to monitor
 371          * @param cleaner the cleaner
 372          * @param action the action Runnable
 373          */
 374         SoftCleanableRef(Object obj, Cleaner cleaner, Runnable action) {
 375             super(obj, cleaner);
 376             this.action = action;
 377         }
 378 
 379         /**
 380          * Constructor used only for root of soft cleanable list.
 381          */
 382         SoftCleanableRef() {
 383             super();
 384             this.action = null;
 385         }
 386 
 387         @Override
 388         protected void performCleanup() {
 389             action.run();
 390         }
 391 
 392         /**
 393          * Prevent access to referent even when it is still alive.
 394          *
 395          * @throws UnsupportedOperationException always
 396          */
 397         @Override
 398         public Object get() {
 399             throw new UnsupportedOperationException("get");
 400         }
 401 
 402         /**
 403          * Direct clearing of the referent is not supported.
 404          *
 405          * @throws UnsupportedOperationException always
 406          */
 407         @Override
 408         public void clear() {
 409             throw new UnsupportedOperationException("clear");
 410         }
 411 
 412     }
 413 
 414     /**
 415      * A ThreadFactory for InnocuousThreads.
 416      * The factory is a singleton.
 417      */
 418     static final class InnocuousThreadFactory implements ThreadFactory {
 419         final static ThreadFactory factory = new InnocuousThreadFactory();
 420 
 421         static ThreadFactory factory() {
 422             return factory;
 423         }
 424 
 425         final AtomicInteger cleanerThreadNumber = new AtomicInteger();
 426 
 427         public Thread newThread(Runnable r) {
 428             return AccessController.doPrivileged(new PrivilegedAction<>() {
 429                 @Override
 430                 public Thread run() {
 431                     Thread t = InnocuousThread.newThread(r);
 432                     t.setPriority(Thread.MAX_PRIORITY - 2);
 433                     t.setName("Cleaner-" + cleanerThreadNumber.getAndIncrement());
 434                     return t;
 435                 }
 436             });
 437         }
 438     }
 439 
 440     /**
 441      * A PhantomCleanable implementation for tracking the Cleaner itself.
 442      */
 443     static final class CleanerCleanable extends PhantomCleanable<Cleaner> {
 444         CleanerCleanable(Cleaner cleaner) {
 445             super(cleaner, cleaner);
 446         }
 447 
 448         @Override
 449         protected void performCleanup() {
 450             // no action
 451         }
 452     }
 453 }