/*
* 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, Peter Jones
/**
* 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 /** 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.
/**
* 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 /** 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.
/**
* 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 /** count of non-permanent objects in table or still processing calls */
private static int keepAliveCount = 0;
thread to collect unreferenced objects from table /** thread to collect unreferenced objects from table */
private static Thread reaper = null;
queue notified when weak refs in the table are cleared /** 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) /** 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.
/**
* 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
/**
* 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 obj passed
as a parameter. This operation is only valid after
the object has been exported.
Throws: - NoSuchObjectException – if the stub for the
remote object could not be found.
Returns: the stub for the remote object, obj.
/**
* 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.
Params: - obj – the remote object to be unexported
- 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
Throws: - NoSuchObjectException – if the remote object is not
currently exported
Returns: true if operation is successful, false otherwise
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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).
/**
* 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).
/**
* 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
}
}
}
}