/* | 
|
 * 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  | 
|
}  | 
|
}  | 
|
}  | 
|
}  |