private
final
Set<String> retainedKeys;
private
final
ReferenceQueue<Object> queue;
/**
* Watches the provided references and checks if it can be GCed. This method is non blocking,
* the check is done on the {@link Executor} this {@link RefWatcher} has been constructed with.
*
* @param referenceName An logical identifier for the watched object.
*/
public
void
watch(Object watchedReference, String referenceName) {
checkNotNull(watchedReference,
"watchedReference"
);
checkNotNull(referenceName,
"referenceName"
);
if
(debuggerControl.isDebuggerAttached()) {
return
;
}
final
long
watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final
KeyedWeakReference reference =
new
KeyedWeakReference(watchedReference, key, referenceName, queue);
watchExecutor.execute(
new
Runnable() {
@Override
public
void
run() {
ensureGone(reference, watchStartNanoTime);
}
});
}
void
ensureGone(KeyedWeakReference reference,
long
watchStartNanoTime) {
long
gcStartNanoTime = System.nanoTime();
long
watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();
if
(gone(reference) || debuggerControl.isDebuggerAttached()) {
return
;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if
(!gone(reference)) {
long
startDumpHeap = System.nanoTime();
long
gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if
(heapDumpFile == HeapDumper.NO_DUMP) {
return
;
}
long
heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
heapdumpListener.analyze(
new
HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
}
private
boolean
gone(KeyedWeakReference reference) {
return
!retainedKeys.contains(reference.key);
}
private
void
removeWeaklyReachableReferences() {
KeyedWeakReference ref;
while
((ref = (KeyedWeakReference) queue.poll()) !=
null
) {
retainedKeys.remove(ref.key);
}
}