1 /* 2 * Copyright (c) 1996, 2015, 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.PhantomReference; 28 import java.lang.ref.ReferenceQueue; 29 import java.net.SocketPermission; 30 import java.security.AccessController; 31 import java.security.PrivilegedAction; 32 import java.util.HashMap; 33 import java.util.HashSet; 34 import java.util.Iterator; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Set; 38 import java.rmi.ConnectException; 39 import java.rmi.RemoteException; 40 import java.rmi.dgc.DGC; 41 import java.rmi.dgc.Lease; 42 import java.rmi.dgc.VMID; 43 import java.rmi.server.ObjID; 44 import sun.misc.GC; 45 import sun.rmi.runtime.NewThreadAction; 46 import sun.rmi.server.UnicastRef; 47 import sun.rmi.server.Util; 48 49 import java.security.AccessControlContext; 50 import java.security.Permissions; 51 import java.security.ProtectionDomain; 52 53 /** 54 * DGCClient implements the client-side of the RMI distributed garbage 55 * collection system. 56 * 57 * The external interface to DGCClient is the "registerRefs" method. 58 * When a LiveRef to a remote object enters the VM, it needs to be 59 * registered with the DGCClient to participate in distributed garbage 60 * collection. 61 * 62 * When the first LiveRef to a particular remote object is registered, 63 * a "dirty" call is made to the server-side distributed garbage 64 * collector for the remote object, which returns a lease guaranteeing 65 * that the server-side DGC will not collect the remote object for a 66 * certain period of time. While LiveRef instances to remote objects 67 * on a particular server exist, the DGCClient periodically sends more 68 * "dirty" calls to renew its lease. 69 * 70 * The DGCClient tracks the local reachability of registered LiveRef 71 * instances (using phantom references). When the LiveRef instance 72 * for a particular remote object becomes garbage collected locally, 73 * a "clean" call is made to the server-side distributed garbage 74 * collector, indicating that the server no longer needs to keep the 75 * remote object alive for this client. 76 * 77 * @see java.rmi.dgc.DGC, sun.rmi.transport.DGCImpl 78 * 79 * @author Ann Wollrath 80 * @author Peter Jones 81 */ 82 final class DGCClient { 83 84 /** next sequence number for DGC calls (access synchronized on class) */ 85 private static long nextSequenceNum = Long.MIN_VALUE; 86 87 /** unique identifier for this VM as a client of DGC */ 88 private static VMID vmid = new VMID(); 89 90 /** lease duration to request (usually ignored by server) */ 91 private static final long leaseValue = // default 10 minutes 92 AccessController.doPrivileged((PrivilegedAction<Long>) () -> 93 Long.getLong("java.rmi.dgc.leaseValue", 600000)); 94 95 /** maximum interval between retries of failed clean calls */ 96 private static final long cleanInterval = // default 3 minutes 97 AccessController.doPrivileged((PrivilegedAction<Long>) () -> 98 Long.getLong("sun.rmi.dgc.cleanInterval", 180000)); 99 100 /** maximum interval between complete garbage collections of local heap */ 101 private static final long gcInterval = // default 1 hour 102 AccessController.doPrivileged((PrivilegedAction<Long>) () -> 103 Long.getLong("sun.rmi.dgc.client.gcInterval", 3600000)); 104 105 /** minimum retry count for dirty calls that fail */ 106 private static final int dirtyFailureRetries = 5; 107 108 /** retry count for clean calls that fail with ConnectException */ 109 private static final int cleanFailureRetries = 5; 110 111 /** constant empty ObjID array for lease renewal optimization */ 112 private static final ObjID[] emptyObjIDArray = new ObjID[0]; 113 114 /** ObjID for server-side DGC object */ 115 private static final ObjID dgcID = new ObjID(ObjID.DGC_ID); 116 117 /** 118 * An AccessControlContext with only socket permissions, 119 * suitable for an RMIClientSocketFactory. 120 */ 121 private static final AccessControlContext SOCKET_ACC; 122 static { 123 Permissions perms = new Permissions(); 124 perms.add(new SocketPermission("*", "connect,resolve")); 125 ProtectionDomain[] pd = { new ProtectionDomain(null, perms) }; 126 SOCKET_ACC = new AccessControlContext(pd); 127 } 128 129 /* 130 * Disallow anyone from creating one of these. 131 */ 132 private DGCClient() {} 133 134 /** 135 * Register the LiveRef instances in the supplied list to participate 136 * in distributed garbage collection. 137 * 138 * All of the LiveRefs in the list must be for remote objects at the 139 * given endpoint. 140 */ 141 static void registerRefs(Endpoint ep, List<LiveRef> refs) { 142 /* 143 * Look up the given endpoint and register the refs with it. 144 * The retrieved entry may get removed from the global endpoint 145 * table before EndpointEntry.registerRefs() is able to acquire 146 * its lock; in this event, it returns false, and we loop and 147 * try again. 148 */ 149 EndpointEntry epEntry; 150 do { 151 epEntry = EndpointEntry.lookup(ep); 152 } while (!epEntry.registerRefs(refs)); 153 } 154 155 /** 156 * Get the next sequence number to be used for a dirty or clean 157 * operation from this VM. This method should only be called while 158 * synchronized on the EndpointEntry whose data structures the 159 * operation affects. 160 */ 161 private static synchronized long getNextSequenceNum() { 162 return nextSequenceNum++; 163 } 164 165 /** 166 * Given the length of a lease and the time that it was granted, 167 * compute the absolute time at which it should be renewed, giving 168 * room for reasonable computational and communication delays. 169 */ 170 private static long computeRenewTime(long grantTime, long duration) { 171 /* 172 * REMIND: This algorithm should be more sophisticated, waiting 173 * a longer fraction of the lease duration for longer leases. 174 */ 175 return grantTime + (duration / 2); 176 } 177 178 /** 179 * EndpointEntry encapsulates the client-side DGC information specific 180 * to a particular Endpoint. Of most significance is the table that 181 * maps LiveRef value to RefEntry objects and the renew/clean thread 182 * that handles asynchronous client-side DGC operations. 183 */ 184 private static class EndpointEntry { 185 186 /** the endpoint that this entry is for */ 187 private Endpoint endpoint; 188 /** synthesized reference to the remote server-side DGC */ 189 private DGC dgc; 190 191 /** table of refs held for endpoint: maps LiveRef to RefEntry */ 192 private Map<LiveRef, RefEntry> refTable = new HashMap<>(5); 193 /** set of RefEntry instances from last (failed) dirty call */ 194 private Set<RefEntry> invalidRefs = new HashSet<>(5); 195 196 /** true if this entry has been removed from the global table */ 197 private boolean removed = false; 198 199 /** absolute time to renew current lease to this endpoint */ 200 private long renewTime = Long.MAX_VALUE; 201 /** absolute time current lease to this endpoint will expire */ 202 private long expirationTime = Long.MIN_VALUE; 203 /** count of recent dirty calls that have failed */ 204 private int dirtyFailures = 0; 205 /** absolute time of first recent failed dirty call */ 206 private long dirtyFailureStartTime; 207 /** (average) elapsed time for recent failed dirty calls */ 208 private long dirtyFailureDuration; 209 210 /** renew/clean thread for handling lease renewals and clean calls */ 211 private Thread renewCleanThread; 212 /** true if renew/clean thread may be interrupted */ 213 private boolean interruptible = false; 214 215 /** reference queue for phantom references */ 216 private ReferenceQueue<LiveRef> refQueue = new ReferenceQueue<>(); 217 /** set of clean calls that need to be made */ 218 private Set<CleanRequest> pendingCleans = new HashSet<>(5); 219 220 /** global endpoint table: maps Endpoint to EndpointEntry */ 221 private static Map<Endpoint,EndpointEntry> endpointTable = new HashMap<>(5); 222 /** handle for GC latency request (for future cancellation) */ 223 private static GC.LatencyRequest gcLatencyRequest = null; 224 225 /** 226 * Look up the EndpointEntry for the given Endpoint. An entry is 227 * created if one does not already exist. 228 */ 229 public static EndpointEntry lookup(Endpoint ep) { 230 synchronized (endpointTable) { 231 EndpointEntry entry = endpointTable.get(ep); 232 if (entry == null) { 233 entry = new EndpointEntry(ep); 234 endpointTable.put(ep, entry); 235 /* 236 * While we are tracking live remote references registered 237 * in this VM, request a maximum latency for inspecting the 238 * entire heap from the local garbage collector, to place 239 * an upper bound on the time to discover remote references 240 * that have become unreachable (see bugid 4171278). 241 */ 242 if (gcLatencyRequest == null) { 243 gcLatencyRequest = GC.requestLatency(gcInterval); 244 } 245 } 246 return entry; 247 } 248 } 249 250 private EndpointEntry(final Endpoint endpoint) { 251 this.endpoint = endpoint; 252 try { 253 LiveRef dgcRef = new LiveRef(dgcID, endpoint, false); 254 dgc = (DGC) Util.createProxy(DGCImpl.class, 255 new UnicastRef(dgcRef), true); 256 } catch (RemoteException e) { 257 throw new Error("internal error creating DGC stub"); 258 } 259 renewCleanThread = AccessController.doPrivileged( 260 new NewThreadAction(new RenewCleanThread(), 261 "RenewClean-" + endpoint, true)); 262 renewCleanThread.start(); 263 } 264 265 /** 266 * Register the LiveRef instances in the supplied list to participate 267 * in distributed garbage collection. 268 * 269 * This method returns false if this entry was removed from the 270 * global endpoint table (because it was empty) before these refs 271 * could be registered. In that case, a new EndpointEntry needs 272 * to be looked up. 273 * 274 * This method must NOT be called while synchronized on this entry. 275 */ 276 public boolean registerRefs(List<LiveRef> refs) { 277 assert !Thread.holdsLock(this); 278 279 Set<RefEntry> refsToDirty = null; // entries for refs needing dirty 280 long sequenceNum; // sequence number for dirty call 281 282 synchronized (this) { 283 if (removed) { 284 return false; 285 } 286 287 Iterator<LiveRef> iter = refs.iterator(); 288 while (iter.hasNext()) { 289 LiveRef ref = iter.next(); 290 assert ref.getEndpoint().equals(endpoint); 291 292 RefEntry refEntry = refTable.get(ref); 293 if (refEntry == null) { 294 LiveRef refClone = (LiveRef) ref.clone(); 295 refEntry = new RefEntry(refClone); 296 refTable.put(refClone, refEntry); 297 if (refsToDirty == null) { 298 refsToDirty = new HashSet<>(5); 299 } 300 refsToDirty.add(refEntry); 301 } 302 303 refEntry.addInstanceToRefSet(ref); 304 } 305 306 if (refsToDirty == null) { 307 return true; 308 } 309 310 refsToDirty.addAll(invalidRefs); 311 invalidRefs.clear(); 312 313 sequenceNum = getNextSequenceNum(); 314 } 315 316 makeDirtyCall(refsToDirty, sequenceNum); 317 return true; 318 } 319 320 /** 321 * Remove the given RefEntry from the ref table. If that makes 322 * the ref table empty, remove this entry from the global endpoint 323 * table. 324 * 325 * This method must ONLY be called while synchronized on this entry. 326 */ 327 private void removeRefEntry(RefEntry refEntry) { 328 assert Thread.holdsLock(this); 329 assert !removed; 330 assert refTable.containsKey(refEntry.getRef()); 331 332 refTable.remove(refEntry.getRef()); 333 invalidRefs.remove(refEntry); 334 if (refTable.isEmpty()) { 335 synchronized (endpointTable) { 336 endpointTable.remove(endpoint); 337 Transport transport = endpoint.getOutboundTransport(); 338 transport.free(endpoint); 339 /* 340 * If there are no longer any live remote references 341 * registered, we are no longer concerned with the 342 * latency of local garbage collection here. 343 */ 344 if (endpointTable.isEmpty()) { 345 assert gcLatencyRequest != null; 346 gcLatencyRequest.cancel(); 347 gcLatencyRequest = null; 348 } 349 removed = true; 350 } 351 } 352 } 353 354 /** 355 * Make a DGC dirty call to this entry's endpoint, for the ObjIDs 356 * corresponding to the given set of refs and with the given 357 * sequence number. 358 * 359 * This method must NOT be called while synchronized on this entry. 360 */ 361 private void makeDirtyCall(Set<RefEntry> refEntries, long sequenceNum) { 362 assert !Thread.holdsLock(this); 363 364 ObjID[] ids; 365 if (refEntries != null) { 366 ids = createObjIDArray(refEntries); 367 } else { 368 ids = emptyObjIDArray; 369 } 370 371 long startTime = System.currentTimeMillis(); 372 try { 373 Lease lease = 374 dgc.dirty(ids, sequenceNum, new Lease(vmid, leaseValue)); 375 long duration = lease.getValue(); 376 377 long newRenewTime = computeRenewTime(startTime, duration); 378 long newExpirationTime = startTime + duration; 379 380 synchronized (this) { 381 dirtyFailures = 0; 382 setRenewTime(newRenewTime); 383 expirationTime = newExpirationTime; 384 } 385 386 } catch (Exception e) { 387 long endTime = System.currentTimeMillis(); 388 389 synchronized (this) { 390 dirtyFailures++; 391 392 if (dirtyFailures == 1) { 393 /* 394 * If this was the first recent failed dirty call, 395 * reschedule another one immediately, in case there 396 * was just a transient network problem, and remember 397 * the start time and duration of this attempt for 398 * future calculations of the delays between retries. 399 */ 400 dirtyFailureStartTime = startTime; 401 dirtyFailureDuration = endTime - startTime; 402 setRenewTime(endTime); 403 } else { 404 /* 405 * For each successive failed dirty call, wait for a 406 * (binary) exponentially increasing delay before 407 * retrying, to avoid network congestion. 408 */ 409 int n = dirtyFailures - 2; 410 if (n == 0) { 411 /* 412 * Calculate the initial retry delay from the 413 * average time elapsed for each of the first 414 * two failed dirty calls. The result must be 415 * at least 1000ms, to prevent a tight loop. 416 */ 417 dirtyFailureDuration = 418 Math.max((dirtyFailureDuration + 419 (endTime - startTime)) >> 1, 1000); 420 } 421 long newRenewTime = 422 endTime + (dirtyFailureDuration << n); 423 424 /* 425 * Continue if the last known held lease has not 426 * expired, or else at least a fixed number of times, 427 * or at least until we've tried for a fixed amount 428 * of time (the default lease value we request). 429 */ 430 if (newRenewTime < expirationTime || 431 dirtyFailures < dirtyFailureRetries || 432 newRenewTime < dirtyFailureStartTime + leaseValue) 433 { 434 setRenewTime(newRenewTime); 435 } else { 436 /* 437 * Give up: postpone lease renewals until next 438 * ref is registered for this endpoint. 439 */ 440 setRenewTime(Long.MAX_VALUE); 441 } 442 } 443 444 if (refEntries != null) { 445 /* 446 * Add all of these refs to the set of refs for this 447 * endpoint that may be invalid (this VM may not be in 448 * the server's referenced set), so that we will 449 * attempt to explicitly dirty them again in the 450 * future. 451 */ 452 invalidRefs.addAll(refEntries); 453 454 /* 455 * Record that a dirty call has failed for all of these 456 * refs, so that clean calls for them in the future 457 * will be strong. 458 */ 459 Iterator<RefEntry> iter = refEntries.iterator(); 460 while (iter.hasNext()) { 461 RefEntry refEntry = iter.next(); 462 refEntry.markDirtyFailed(); 463 } 464 } 465 466 /* 467 * If the last known held lease will have expired before 468 * the next renewal, all refs might be invalid. 469 */ 470 if (renewTime >= expirationTime) { 471 invalidRefs.addAll(refTable.values()); 472 } 473 } 474 } 475 } 476 477 /** 478 * Set the absolute time at which the lease for this entry should 479 * be renewed. 480 * 481 * This method must ONLY be called while synchronized on this entry. 482 */ 483 private void setRenewTime(long newRenewTime) { 484 assert Thread.holdsLock(this); 485 486 if (newRenewTime < renewTime) { 487 renewTime = newRenewTime; 488 if (interruptible) { 489 AccessController.doPrivileged( 490 new PrivilegedAction<Void>() { 491 public Void run() { 492 renewCleanThread.interrupt(); 493 return null; 494 } 495 }); 496 } 497 } else { 498 renewTime = newRenewTime; 499 } 500 } 501 502 /** 503 * RenewCleanThread handles the asynchronous client-side DGC activity 504 * for this entry: renewing the leases and making clean calls. 505 */ 506 private class RenewCleanThread implements Runnable { 507 508 public void run() { 509 do { 510 long timeToWait; 511 RefEntry.PhantomLiveRef phantom = null; 512 boolean needRenewal = false; 513 Set<RefEntry> refsToDirty = null; 514 long sequenceNum = Long.MIN_VALUE; 515 516 synchronized (EndpointEntry.this) { 517 /* 518 * Calculate time to block (waiting for phantom 519 * reference notifications). It is the time until the 520 * lease renewal should be done, bounded on the low 521 * end by 1 ms so that the reference queue will always 522 * get processed, and if there are pending clean 523 * requests (remaining because some clean calls 524 * failed), bounded on the high end by the maximum 525 * clean call retry interval. 526 */ 527 long timeUntilRenew = 528 renewTime - System.currentTimeMillis(); 529 timeToWait = Math.max(timeUntilRenew, 1); 530 if (!pendingCleans.isEmpty()) { 531 timeToWait = Math.min(timeToWait, cleanInterval); 532 } 533 534 /* 535 * Set flag indicating that it is OK to interrupt this 536 * thread now, such as if a earlier lease renewal time 537 * is set, because we are only going to be blocking 538 * and can deal with interrupts. 539 */ 540 interruptible = true; 541 } 542 543 try { 544 /* 545 * Wait for the duration calculated above for any of 546 * our phantom references to be enqueued. 547 */ 548 phantom = (RefEntry.PhantomLiveRef) 549 refQueue.remove(timeToWait); 550 } catch (InterruptedException e) { 551 } 552 553 synchronized (EndpointEntry.this) { 554 /* 555 * Set flag indicating that it is NOT OK to interrupt 556 * this thread now, because we may be undertaking I/O 557 * operations that should not be interrupted (and we 558 * will not be blocking arbitrarily). 559 */ 560 interruptible = false; 561 Thread.interrupted(); // clear interrupted state 562 563 /* 564 * If there was a phantom reference enqueued, process 565 * it and all the rest on the queue, generating 566 * clean requests as necessary. 567 */ 568 if (phantom != null) { 569 processPhantomRefs(phantom); 570 } 571 572 /* 573 * Check if it is time to renew this entry's lease. 574 */ 575 long currentTime = System.currentTimeMillis(); 576 if (currentTime > renewTime) { 577 needRenewal = true; 578 if (!invalidRefs.isEmpty()) { 579 refsToDirty = invalidRefs; 580 invalidRefs = new HashSet<>(5); 581 } 582 sequenceNum = getNextSequenceNum(); 583 } 584 } 585 586 boolean needRenewal_ = needRenewal; 587 Set<RefEntry> refsToDirty_ = refsToDirty; 588 long sequenceNum_ = sequenceNum; 589 AccessController.doPrivileged((PrivilegedAction<Void>)() -> { 590 if (needRenewal_) { 591 makeDirtyCall(refsToDirty_, sequenceNum_); 592 } 593 594 if (!pendingCleans.isEmpty()) { 595 makeCleanCalls(); 596 } 597 return null; 598 }, SOCKET_ACC); 599 } while (!removed || !pendingCleans.isEmpty()); 600 } 601 } 602 603 /** 604 * Process the notification of the given phantom reference and any 605 * others that are on this entry's reference queue. Each phantom 606 * reference is removed from its RefEntry's ref set. All ref 607 * entries that have no more registered instances are collected 608 * into up to two batched clean call requests: one for refs 609 * requiring a "strong" clean call, and one for the rest. 610 * 611 * This method must ONLY be called while synchronized on this entry. 612 */ 613 private void processPhantomRefs(RefEntry.PhantomLiveRef phantom) { 614 assert Thread.holdsLock(this); 615 616 Set<RefEntry> strongCleans = null; 617 Set<RefEntry> normalCleans = null; 618 619 do { 620 RefEntry refEntry = phantom.getRefEntry(); 621 refEntry.removeInstanceFromRefSet(phantom); 622 if (refEntry.isRefSetEmpty()) { 623 if (refEntry.hasDirtyFailed()) { 624 if (strongCleans == null) { 625 strongCleans = new HashSet<>(5); 626 } 627 strongCleans.add(refEntry); 628 } else { 629 if (normalCleans == null) { 630 normalCleans = new HashSet<>(5); 631 } 632 normalCleans.add(refEntry); 633 } 634 removeRefEntry(refEntry); 635 } 636 } while ((phantom = 637 (RefEntry.PhantomLiveRef) refQueue.poll()) != null); 638 639 if (strongCleans != null) { 640 pendingCleans.add( 641 new CleanRequest(createObjIDArray(strongCleans), 642 getNextSequenceNum(), true)); 643 } 644 if (normalCleans != null) { 645 pendingCleans.add( 646 new CleanRequest(createObjIDArray(normalCleans), 647 getNextSequenceNum(), false)); 648 } 649 } 650 651 /** 652 * CleanRequest holds the data for the parameters of a clean call 653 * that needs to be made. 654 */ 655 private static class CleanRequest { 656 657 final ObjID[] objIDs; 658 final long sequenceNum; 659 final boolean strong; 660 661 /** how many times this request has failed */ 662 int failures = 0; 663 664 CleanRequest(ObjID[] objIDs, long sequenceNum, boolean strong) { 665 this.objIDs = objIDs; 666 this.sequenceNum = sequenceNum; 667 this.strong = strong; 668 } 669 } 670 671 /** 672 * Make all of the clean calls described by the clean requests in 673 * this entry's set of "pending cleans". Clean requests for clean 674 * calls that succeed are removed from the "pending cleans" set. 675 * 676 * This method must NOT be called while synchronized on this entry. 677 */ 678 private void makeCleanCalls() { 679 assert !Thread.holdsLock(this); 680 681 Iterator<CleanRequest> iter = pendingCleans.iterator(); 682 while (iter.hasNext()) { 683 CleanRequest request = iter.next(); 684 try { 685 dgc.clean(request.objIDs, request.sequenceNum, vmid, 686 request.strong); 687 iter.remove(); 688 } catch (Exception e) { 689 /* 690 * Many types of exceptions here could have been 691 * caused by a transient failure, so try again a 692 * few times, but not forever. 693 */ 694 if (++request.failures >= cleanFailureRetries) { 695 iter.remove(); 696 } 697 } 698 } 699 } 700 701 /** 702 * Create an array of ObjIDs (needed for the DGC remote calls) 703 * from the ids in the given set of refs. 704 */ 705 private static ObjID[] createObjIDArray(Set<RefEntry> refEntries) { 706 ObjID[] ids = new ObjID[refEntries.size()]; 707 Iterator<RefEntry> iter = refEntries.iterator(); 708 for (int i = 0; i < ids.length; i++) { 709 ids[i] = iter.next().getRef().getObjID(); 710 } 711 return ids; 712 } 713 714 /** 715 * RefEntry encapsulates the client-side DGC information specific 716 * to a particular LiveRef value. In particular, it contains a 717 * set of phantom references to all of the instances of the LiveRef 718 * value registered in the system (but not garbage collected 719 * locally). 720 */ 721 private class RefEntry { 722 723 /** LiveRef value for this entry (not a registered instance) */ 724 private LiveRef ref; 725 /** set of phantom references to registered instances */ 726 private Set<PhantomLiveRef> refSet = new HashSet<>(5); 727 /** true if a dirty call containing this ref has failed */ 728 private boolean dirtyFailed = false; 729 730 public RefEntry(LiveRef ref) { 731 this.ref = ref; 732 } 733 734 /** 735 * Return the LiveRef value for this entry (not a registered 736 * instance). 737 */ 738 public LiveRef getRef() { 739 return ref; 740 } 741 742 /** 743 * Add a LiveRef to the set of registered instances for this entry. 744 * 745 * This method must ONLY be invoked while synchronized on this 746 * RefEntry's EndpointEntry. 747 */ 748 public void addInstanceToRefSet(LiveRef ref) { 749 assert Thread.holdsLock(EndpointEntry.this); 750 assert ref.equals(this.ref); 751 752 /* 753 * Only keep a phantom reference to the registered instance, 754 * so that it can be garbage collected normally (and we can be 755 * notified when that happens). 756 */ 757 refSet.add(new PhantomLiveRef(ref)); 758 } 759 760 /** 761 * Remove a PhantomLiveRef from the set of registered instances. 762 * 763 * This method must ONLY be invoked while synchronized on this 764 * RefEntry's EndpointEntry. 765 */ 766 public void removeInstanceFromRefSet(PhantomLiveRef phantom) { 767 assert Thread.holdsLock(EndpointEntry.this); 768 assert refSet.contains(phantom); 769 refSet.remove(phantom); 770 } 771 772 /** 773 * Return true if there are no registered LiveRef instances for 774 * this entry still reachable in this VM. 775 * 776 * This method must ONLY be invoked while synchronized on this 777 * RefEntry's EndpointEntry. 778 */ 779 public boolean isRefSetEmpty() { 780 assert Thread.holdsLock(EndpointEntry.this); 781 return refSet.size() == 0; 782 } 783 784 /** 785 * Record that a dirty call that explicitly contained this 786 * entry's ref has failed. 787 * 788 * This method must ONLY be invoked while synchronized on this 789 * RefEntry's EndpointEntry. 790 */ 791 public void markDirtyFailed() { 792 assert Thread.holdsLock(EndpointEntry.this); 793 dirtyFailed = true; 794 } 795 796 /** 797 * Return true if a dirty call that explicitly contained this 798 * entry's ref has failed (and therefore a clean call for this 799 * ref needs to be marked "strong"). 800 * 801 * This method must ONLY be invoked while synchronized on this 802 * RefEntry's EndpointEntry. 803 */ 804 public boolean hasDirtyFailed() { 805 assert Thread.holdsLock(EndpointEntry.this); 806 return dirtyFailed; 807 } 808 809 /** 810 * PhantomLiveRef is a PhantomReference to a LiveRef instance, 811 * used to detect when the LiveRef becomes permanently 812 * unreachable in this VM. 813 */ 814 private class PhantomLiveRef extends PhantomReference<LiveRef> { 815 816 public PhantomLiveRef(LiveRef ref) { 817 super(ref, EndpointEntry.this.refQueue); 818 } 819 820 public RefEntry getRefEntry() { 821 return RefEntry.this; 822 } 823 } 824 } 825 } 826 }