1 /*
   2  * Copyright (c) 1996, 2005, 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 package sun.rmi.transport;
  26 
  27 import java.lang.ref.ReferenceQueue;
  28 import java.rmi.NoSuchObjectException;
  29 import java.rmi.Remote;
  30 import java.rmi.dgc.VMID;
  31 import java.rmi.server.ExportException;
  32 import java.rmi.server.ObjID;
  33 import java.security.AccessController;
  34 import java.security.PrivilegedAction;
  35 import java.util.HashMap;
  36 import java.util.Map;
  37 import sun.misc.GC;
  38 import sun.rmi.runtime.Log;
  39 import sun.rmi.runtime.NewThreadAction;
  40 import sun.security.action.GetLongAction;
  41 
  42 /**
  43  * Object table shared by all implementors of the Transport interface.
  44  * This table maps object ids to remote object targets in this address
  45  * space.
  46  *
  47  * @author  Ann Wollrath
  48  * @author  Peter Jones
  49  */
  50 public final class ObjectTable {
  51 
  52     /** maximum interval between complete garbage collections of local heap */
  53     private final static long gcInterval =              // default 1 hour
  54         AccessController.doPrivileged(
  55             new GetLongAction("sun.rmi.dgc.server.gcInterval", 3600000));
  56 
  57     /**
  58      * lock guarding objTable and implTable.
  59      * Holders MAY acquire a Target instance's lock or keepAliveLock.
  60      */
  61     private static final Object tableLock = new Object();
  62 
  63     /** tables mapping to Target, keyed from ObjectEndpoint and impl object */
  64     private static final Map<ObjectEndpoint,Target> objTable =
  65         new HashMap<ObjectEndpoint,Target>();
  66     private static final Map<WeakRef,Target> implTable =
  67         new HashMap<WeakRef,Target>();
  68 
  69     /**
  70      * lock guarding keepAliveCount, reaper, and gcLatencyRequest.
  71      * Holders may NOT acquire a Target instance's lock or tableLock.
  72      */
  73     private static final Object keepAliveLock = new Object();
  74 
  75     /** count of non-permanent objects in table or still processing calls */
  76     private static int keepAliveCount = 0;
  77 
  78     /** thread to collect unreferenced objects from table */
  79     private static Thread reaper = null;
  80 
  81     /** queue notified when weak refs in the table are cleared */
  82     static final ReferenceQueue reapQueue = new ReferenceQueue();
  83 
  84     /** handle for GC latency request (for future cancellation) */
  85     private static GC.LatencyRequest gcLatencyRequest = null;
  86 
  87     /*
  88      * Disallow anyone from creating one of these.
  89      */
  90     private ObjectTable() {}
  91 
  92     /**
  93      * Returns the target associated with the object id.
  94      */
  95     static Target getTarget(ObjectEndpoint oe) {
  96         synchronized (tableLock) {
  97             return objTable.get(oe);
  98         }
  99     }
 100 
 101     /**
 102      * Returns the target associated with the remote object
 103      */
 104     public static Target getTarget(Remote impl) {
 105         synchronized (tableLock) {
 106             return implTable.get(new WeakRef(impl));
 107         }
 108     }
 109 
 110     /**
 111      * Returns the stub for the remote object <b>obj</b> passed
 112      * as a parameter. This operation is only valid <i>after</i>
 113      * the object has been exported.
 114      *
 115      * @return the stub for the remote object, <b>obj</b>.
 116      * @exception NoSuchObjectException if the stub for the
 117      * remote object could not be found.
 118      */
 119     public static Remote getStub(Remote impl)
 120         throws NoSuchObjectException
 121     {
 122         Target target = getTarget(impl);
 123         if (target == null) {
 124             throw new NoSuchObjectException("object not exported");
 125         } else {
 126             return target.getStub();
 127         }
 128     }
 129 
 130    /**
 131     * Remove the remote object, obj, from the RMI runtime. If
 132     * successful, the object can no longer accept incoming RMI calls.
 133     * If the force parameter is true, the object is forcibly unexported
 134     * even if there are pending calls to the remote object or the
 135     * remote object still has calls in progress.  If the force
 136     * parameter is false, the object is only unexported if there are
 137     * no pending or in progress calls to the object.
 138     *
 139     * @param obj the remote object to be unexported
 140     * @param force if true, unexports the object even if there are
 141     * pending or in-progress calls; if false, only unexports the object
 142     * if there are no pending or in-progress calls
 143     * @return true if operation is successful, false otherwise
 144     * @exception NoSuchObjectException if the remote object is not
 145     * currently exported
 146     */
 147    public static boolean unexportObject(Remote obj, boolean force)
 148         throws java.rmi.NoSuchObjectException
 149     {
 150         synchronized (tableLock) {
 151             Target target = getTarget(obj);
 152             if (target == null) {
 153                 throw new NoSuchObjectException("object not exported");
 154             } else {
 155                 if (target.unexport(force)) {
 156                     removeTarget(target);
 157                     return true;
 158                 } else {
 159                     return false;
 160                 }
 161             }
 162         }
 163     }
 164 
 165     /**
 166      * Add target to object table.  If it is not a permanent entry, then
 167      * make sure that reaper thread is running to remove collected entries
 168      * and keep VM alive.
 169      */
 170     static void putTarget(Target target) throws ExportException {
 171         ObjectEndpoint oe = target.getObjectEndpoint();
 172         WeakRef weakImpl = target.getWeakImpl();
 173 
 174         if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) {
 175             DGCImpl.dgcLog.log(Log.VERBOSE, "add object " + oe);
 176         }
 177 
 178         Remote impl = target.getImpl();
 179         if (impl == null) {
 180             throw new ExportException(
 181                 "internal error: attempt to export collected object");
 182         }
 183 
 184         synchronized (tableLock) {
 185             if (objTable.containsKey(oe)) {
 186                 throw new ExportException(
 187                     "internal error: ObjID already in use");
 188             } else if (implTable.containsKey(weakImpl)) {
 189                 throw new ExportException("object already exported");
 190             }
 191 
 192             objTable.put(oe, target);
 193             implTable.put(weakImpl, target);
 194 
 195             if (!target.isPermanent()) {
 196                 incrementKeepAliveCount();
 197             }
 198         }
 199     }
 200 
 201     /**
 202      * Remove target from object table.
 203      *
 204      * NOTE: This method must only be invoked while synchronized on
 205      * the "tableLock" object, because it does not do so itself.
 206      */
 207     private static void removeTarget(Target target) {
 208         // assert Thread.holdsLock(tableLock);
 209 
 210         ObjectEndpoint oe = target.getObjectEndpoint();
 211         WeakRef weakImpl = target.getWeakImpl();
 212 
 213         if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) {
 214             DGCImpl.dgcLog.log(Log.VERBOSE, "remove object " + oe);
 215         }
 216 
 217         objTable.remove(oe);
 218         implTable.remove(weakImpl);
 219 
 220         target.markRemoved();   // handles decrementing keep-alive count
 221     }
 222 
 223     /**
 224      * Process client VM signalling reference for given ObjID: forward to
 225      * correspoding Target entry.  If ObjID is not found in table,
 226      * no action is taken.
 227      */
 228     static void referenced(ObjID id, long sequenceNum, VMID vmid) {
 229         synchronized (tableLock) {
 230             ObjectEndpoint oe =
 231                 new ObjectEndpoint(id, Transport.currentTransport());
 232             Target target = objTable.get(oe);
 233             if (target != null) {
 234                 target.referenced(sequenceNum, vmid);
 235             }
 236         }
 237     }
 238 
 239     /**
 240      * Process client VM dropping reference for given ObjID: forward to
 241      * correspoding Target entry.  If ObjID is not found in table,
 242      * no action is taken.
 243      */
 244     static void unreferenced(ObjID id, long sequenceNum, VMID vmid,
 245                              boolean strong)
 246     {
 247         synchronized (tableLock) {
 248             ObjectEndpoint oe =
 249                 new ObjectEndpoint(id, Transport.currentTransport());
 250             Target target = objTable.get(oe);
 251             if (target != null)
 252                 target.unreferenced(sequenceNum, vmid, strong);
 253         }
 254     }
 255 
 256     /**
 257      * Increments the "keep-alive count".
 258      *
 259      * The "keep-alive count" is the number of non-permanent remote objects
 260      * that are either in the object table or still have calls in progress.
 261      * Therefore, this method should be invoked exactly once for every
 262      * non-permanent remote object exported (a remote object must be
 263      * exported before it can have any calls in progress).
 264      *
 265      * The VM is "kept alive" while the keep-alive count is greater than
 266      * zero; this is accomplished by keeping a non-daemon thread running.
 267      *
 268      * Because non-permanent objects are those that can be garbage
 269      * collected while exported, and thus those for which the "reaper"
 270      * thread operates, the reaper thread also serves as the non-daemon
 271      * VM keep-alive thread; a new reaper thread is created if necessary.
 272      */
 273     static void incrementKeepAliveCount() {
 274         synchronized (keepAliveLock) {
 275             keepAliveCount++;
 276 
 277             if (reaper == null) {
 278                 reaper = AccessController.doPrivileged(
 279                     new NewThreadAction(new Reaper(), "Reaper", false));
 280                 reaper.start();
 281             }
 282 
 283             /*
 284              * While there are non-"permanent" objects in the object table,
 285              * request a maximum latency for inspecting the entire heap
 286              * from the local garbage collector, to place an upper bound
 287              * on the time to discover remote objects that have become
 288              * unreachable (and thus can be removed from the table).
 289              */
 290             if (gcLatencyRequest == null) {
 291                 gcLatencyRequest = GC.requestLatency(gcInterval);
 292             }
 293         }
 294     }
 295 
 296     /**
 297      * Decrements the "keep-alive count".
 298      *
 299      * The "keep-alive count" is the number of non-permanent remote objects
 300      * that are either in the object table or still have calls in progress.
 301      * Therefore, this method should be invoked exactly once for every
 302      * previously-exported non-permanent remote object that both has been
 303      * removed from the object table and has no calls still in progress.
 304      *
 305      * If the keep-alive count is decremented to zero, then the current
 306      * reaper thread is terminated to cease keeping the VM alive (and
 307      * because there are no more non-permanent remote objects to reap).
 308      */
 309     static void decrementKeepAliveCount() {
 310         synchronized (keepAliveLock) {
 311             keepAliveCount--;
 312 
 313             if (keepAliveCount == 0) {
 314                 if (!(reaper != null)) { throw new AssertionError(); }
 315                 AccessController.doPrivileged(new PrivilegedAction<Void>() {
 316                     public Void run() {
 317                         reaper.interrupt();
 318                         return null;
 319                     }
 320                 });
 321                 reaper = null;
 322 
 323                 /*
 324                  * If there are no longer any non-permanent objects in the
 325                  * object table, we are no longer concerned with the latency
 326                  * of local garbage collection here.
 327                  */
 328                 gcLatencyRequest.cancel();
 329                 gcLatencyRequest = null;
 330             }
 331         }
 332     }
 333 
 334     /**
 335      * The Reaper thread waits for notifications that weak references in the
 336      * object table have been cleared.  When it receives a notification, it
 337      * removes the corresponding entry from the table.
 338      *
 339      * Since the Reaper is created as a non-daemon thread, it also serves
 340      * to keep the VM from exiting while there are objects in the table
 341      * (other than permanent entries that should neither be reaped nor
 342      * keep the VM alive).
 343      */
 344     private static class Reaper implements Runnable {
 345 
 346         public void run() {
 347             try {
 348                 do {
 349                     // wait for next cleared weak reference
 350                     WeakRef weakImpl = (WeakRef) reapQueue.remove();
 351 
 352                     synchronized (tableLock) {
 353                         Target target = implTable.get(weakImpl);
 354                         if (target != null) {
 355                             if (!target.isEmpty()) {
 356                                 throw new Error(
 357                                     "object with known references collected");
 358                             } else if (target.isPermanent()) {
 359                                 throw new Error("permanent object collected");
 360                             }
 361                             removeTarget(target);
 362                         }
 363                     }
 364                 } while (!Thread.interrupted());
 365             } catch (InterruptedException e) {
 366                 // pass away if interrupted
 367             }
 368         }
 369     }
 370 }