| /* |
| * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| package sun.rmi.transport; |
| |
| import java.lang.ref.ReferenceQueue; |
| import java.rmi.NoSuchObjectException; |
| import java.rmi.Remote; |
| import java.rmi.dgc.VMID; |
| import java.rmi.server.ExportException; |
| import java.rmi.server.ObjID; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.util.HashMap; |
| import java.util.Map; |
| import sun.misc.GC; |
| import sun.rmi.runtime.Log; |
| import sun.rmi.runtime.NewThreadAction; |
| import sun.security.action.GetLongAction; |
| |
| /** |
| * Object table shared by all implementors of the Transport interface. |
| * This table maps object ids to remote object targets in this address |
| * space. |
| * |
| * @author Ann Wollrath |
| * @author Peter Jones |
| */ |
| public final class ObjectTable { |
| |
| /** maximum interval between complete garbage collections of local heap */ |
| private final static long gcInterval = // default 1 hour |
| AccessController.doPrivileged( |
| new GetLongAction("sun.rmi.dgc.server.gcInterval", 3600000)); |
| |
| /** |
| * lock guarding objTable and implTable. |
| * Holders MAY acquire a Target instance's lock or keepAliveLock. |
| */ |
| private static final Object tableLock = new Object(); |
| |
| /** tables mapping to Target, keyed from ObjectEndpoint and impl object */ |
| private static final Map<ObjectEndpoint,Target> objTable = |
| new HashMap<>(); |
| private static final Map<WeakRef,Target> implTable = |
| new HashMap<>(); |
| |
| /** |
| * lock guarding keepAliveCount, reaper, and gcLatencyRequest. |
| * Holders may NOT acquire a Target instance's lock or tableLock. |
| */ |
| private static final Object keepAliveLock = new Object(); |
| |
| /** count of non-permanent objects in table or still processing calls */ |
| private static int keepAliveCount = 0; |
| |
| /** thread to collect unreferenced objects from table */ |
| private static Thread reaper = null; |
| |
| /** queue notified when weak refs in the table are cleared */ |
| static final ReferenceQueue<Object> reapQueue = new ReferenceQueue<>(); |
| |
| /** handle for GC latency request (for future cancellation) */ |
| private static GC.LatencyRequest gcLatencyRequest = null; |
| |
| /* |
| * Disallow anyone from creating one of these. |
| */ |
| private ObjectTable() {} |
| |
| /** |
| * Returns the target associated with the object id. |
| */ |
| static Target getTarget(ObjectEndpoint oe) { |
| synchronized (tableLock) { |
| return objTable.get(oe); |
| } |
| } |
| |
| /** |
| * Returns the target associated with the remote object |
| */ |
| public static Target getTarget(Remote impl) { |
| synchronized (tableLock) { |
| return implTable.get(new WeakRef(impl)); |
| } |
| } |
| |
| /** |
| * Returns the stub for the remote object <b>obj</b> passed |
| * as a parameter. This operation is only valid <i>after</i> |
| * the object has been exported. |
| * |
| * @return the stub for the remote object, <b>obj</b>. |
| * @exception NoSuchObjectException if the stub for the |
| * remote object could not be found. |
| */ |
| public static Remote getStub(Remote impl) |
| throws NoSuchObjectException |
| { |
| Target target = getTarget(impl); |
| if (target == null) { |
| throw new NoSuchObjectException("object not exported"); |
| } else { |
| return target.getStub(); |
| } |
| } |
| |
| /** |
| * Remove the remote object, obj, from the RMI runtime. If |
| * successful, the object can no longer accept incoming RMI calls. |
| * If the force parameter is true, the object is forcibly unexported |
| * even if there are pending calls to the remote object or the |
| * remote object still has calls in progress. If the force |
| * parameter is false, the object is only unexported if there are |
| * no pending or in progress calls to the object. |
| * |
| * @param obj the remote object to be unexported |
| * @param force if true, unexports the object even if there are |
| * pending or in-progress calls; if false, only unexports the object |
| * if there are no pending or in-progress calls |
| * @return true if operation is successful, false otherwise |
| * @exception NoSuchObjectException if the remote object is not |
| * currently exported |
| */ |
| public static boolean unexportObject(Remote obj, boolean force) |
| throws java.rmi.NoSuchObjectException |
| { |
| synchronized (tableLock) { |
| Target target = getTarget(obj); |
| if (target == null) { |
| throw new NoSuchObjectException("object not exported"); |
| } else { |
| if (target.unexport(force)) { |
| removeTarget(target); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Add target to object table. If it is not a permanent entry, then |
| * make sure that reaper thread is running to remove collected entries |
| * and keep VM alive. |
| */ |
| static void putTarget(Target target) throws ExportException { |
| ObjectEndpoint oe = target.getObjectEndpoint(); |
| WeakRef weakImpl = target.getWeakImpl(); |
| |
| if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) { |
| DGCImpl.dgcLog.log(Log.VERBOSE, "add object " + oe); |
| } |
| |
| synchronized (tableLock) { |
| /** |
| * Do nothing if impl has already been collected (see 6597112). Check while |
| * holding tableLock to ensure that Reaper cannot process weakImpl in between |
| * null check and put/increment effects. |
| */ |
| if (target.getImpl() != null) { |
| if (objTable.containsKey(oe)) { |
| throw new ExportException( |
| "internal error: ObjID already in use"); |
| } else if (implTable.containsKey(weakImpl)) { |
| throw new ExportException("object already exported"); |
| } |
| |
| objTable.put(oe, target); |
| implTable.put(weakImpl, target); |
| |
| if (!target.isPermanent()) { |
| incrementKeepAliveCount(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Remove target from object table. |
| * |
| * NOTE: This method must only be invoked while synchronized on |
| * the "tableLock" object, because it does not do so itself. |
| */ |
| private static void removeTarget(Target target) { |
| // assert Thread.holdsLock(tableLock); |
| |
| ObjectEndpoint oe = target.getObjectEndpoint(); |
| WeakRef weakImpl = target.getWeakImpl(); |
| |
| if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) { |
| DGCImpl.dgcLog.log(Log.VERBOSE, "remove object " + oe); |
| } |
| |
| objTable.remove(oe); |
| implTable.remove(weakImpl); |
| |
| target.markRemoved(); // handles decrementing keep-alive count |
| } |
| |
| /** |
| * Process client VM signalling reference for given ObjID: forward to |
| * corresponding Target entry. If ObjID is not found in table, |
| * no action is taken. |
| */ |
| static void referenced(ObjID id, long sequenceNum, VMID vmid) { |
| synchronized (tableLock) { |
| ObjectEndpoint oe = |
| new ObjectEndpoint(id, Transport.currentTransport()); |
| Target target = objTable.get(oe); |
| if (target != null) { |
| target.referenced(sequenceNum, vmid); |
| } |
| } |
| } |
| |
| /** |
| * Process client VM dropping reference for given ObjID: forward to |
| * corresponding Target entry. If ObjID is not found in table, |
| * no action is taken. |
| */ |
| static void unreferenced(ObjID id, long sequenceNum, VMID vmid, |
| boolean strong) |
| { |
| synchronized (tableLock) { |
| ObjectEndpoint oe = |
| new ObjectEndpoint(id, Transport.currentTransport()); |
| Target target = objTable.get(oe); |
| if (target != null) |
| target.unreferenced(sequenceNum, vmid, strong); |
| } |
| } |
| |
| /** |
| * Increments the "keep-alive count". |
| * |
| * The "keep-alive count" is the number of non-permanent remote objects |
| * that are either in the object table or still have calls in progress. |
| * Therefore, this method should be invoked exactly once for every |
| * non-permanent remote object exported (a remote object must be |
| * exported before it can have any calls in progress). |
| * |
| * The VM is "kept alive" while the keep-alive count is greater than |
| * zero; this is accomplished by keeping a non-daemon thread running. |
| * |
| * Because non-permanent objects are those that can be garbage |
| * collected while exported, and thus those for which the "reaper" |
| * thread operates, the reaper thread also serves as the non-daemon |
| * VM keep-alive thread; a new reaper thread is created if necessary. |
| */ |
| static void incrementKeepAliveCount() { |
| synchronized (keepAliveLock) { |
| keepAliveCount++; |
| |
| if (reaper == null) { |
| reaper = AccessController.doPrivileged( |
| new NewThreadAction(new Reaper(), "Reaper", false)); |
| reaper.start(); |
| } |
| |
| /* |
| * While there are non-"permanent" objects in the object table, |
| * request a maximum latency for inspecting the entire heap |
| * from the local garbage collector, to place an upper bound |
| * on the time to discover remote objects that have become |
| * unreachable (and thus can be removed from the table). |
| */ |
| if (gcLatencyRequest == null) { |
| gcLatencyRequest = GC.requestLatency(gcInterval); |
| } |
| } |
| } |
| |
| /** |
| * Decrements the "keep-alive count". |
| * |
| * The "keep-alive count" is the number of non-permanent remote objects |
| * that are either in the object table or still have calls in progress. |
| * Therefore, this method should be invoked exactly once for every |
| * previously-exported non-permanent remote object that both has been |
| * removed from the object table and has no calls still in progress. |
| * |
| * If the keep-alive count is decremented to zero, then the current |
| * reaper thread is terminated to cease keeping the VM alive (and |
| * because there are no more non-permanent remote objects to reap). |
| */ |
| static void decrementKeepAliveCount() { |
| synchronized (keepAliveLock) { |
| keepAliveCount--; |
| |
| if (keepAliveCount == 0) { |
| if (!(reaper != null)) { throw new AssertionError(); } |
| AccessController.doPrivileged(new PrivilegedAction<Void>() { |
| public Void run() { |
| reaper.interrupt(); |
| return null; |
| } |
| }); |
| reaper = null; |
| |
| /* |
| * If there are no longer any non-permanent objects in the |
| * object table, we are no longer concerned with the latency |
| * of local garbage collection here. |
| */ |
| gcLatencyRequest.cancel(); |
| gcLatencyRequest = null; |
| } |
| } |
| } |
| |
| /** |
| * The Reaper thread waits for notifications that weak references in the |
| * object table have been cleared. When it receives a notification, it |
| * removes the corresponding entry from the table. |
| * |
| * Since the Reaper is created as a non-daemon thread, it also serves |
| * to keep the VM from exiting while there are objects in the table |
| * (other than permanent entries that should neither be reaped nor |
| * keep the VM alive). |
| */ |
| private static class Reaper implements Runnable { |
| |
| public void run() { |
| try { |
| do { |
| // wait for next cleared weak reference |
| WeakRef weakImpl = (WeakRef) reapQueue.remove(); |
| |
| synchronized (tableLock) { |
| Target target = implTable.get(weakImpl); |
| if (target != null) { |
| if (!target.isEmpty()) { |
| throw new Error( |
| "object with known references collected"); |
| } else if (target.isPermanent()) { |
| throw new Error("permanent object collected"); |
| } |
| removeTarget(target); |
| } |
| } |
| } while (!Thread.interrupted()); |
| } catch (InterruptedException e) { |
| // pass away if interrupted |
| } |
| } |
| } |
| } |