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