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