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