diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/Resource.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/Resource.java index 5d61855544b..75223b4a92f 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/Resource.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/Resource.java @@ -13,7 +13,9 @@ *******************************************************************************/ package org.eclipse.swt.graphics; +import java.lang.ref.*; import java.util.*; +import java.util.concurrent.atomic.*; import java.util.function.*; import org.eclipse.swt.*; @@ -44,41 +46,30 @@ public abstract class Resource { /** - * Used to track not disposed SWT resource. A separate class allows - * not to have the {@link #finalize} when tracking is disabled, avoiding - * possible performance issues in GC. + * Used to track not disposed SWT resource. */ - private static class ResourceTracker { - /** - * Resource that is tracked here - */ - private Resource resource; - + private static class ResourceTracker implements Runnable { /** * Recorded at Resource creation if {@link #setNonDisposeHandler} was * enabled, used to track resource disposal */ - private Error allocationStack; + private final Error allocationStack; /** - * Allows to ignore specific Resources even if they are not disposed - * properly, used for example for Fonts that SWT doesn't own. + * Controls whether the {@link Resource#nonDisposedReporter} should be notified */ - boolean ignoreMe; + private final AtomicBoolean reporting = new AtomicBoolean(false); - ResourceTracker(Resource resource, Error allocationStack) { - this.resource = resource; + ResourceTracker(Error allocationStack) { this.allocationStack = allocationStack; } @Override - protected void finalize() { - if (ignoreMe) return; + public void run() { + if (!reporting.get()) return; if (nonDisposedReporter == null) return; - // If the Resource is GC'ed before it was disposed, this is a leak. - if (!resource.isDisposed()) - nonDisposedReporter.accept(allocationStack); + nonDisposedReporter.accept(allocationStack); } } @@ -87,6 +78,11 @@ protected void finalize() { */ Device device; + /** + * Invokes {@link ResourceTracker#run()} once the resource is eligible for GC + */ + private static final Cleaner cleaner = Cleaner.create(); + /** * Used to report not disposed SWT resources, null by default */ @@ -142,6 +138,7 @@ void destroyHandlesExcept(Set zoomLevels) { * This method does nothing if the resource is already disposed. */ public void dispose() { + if (tracker != null) tracker.reporting.set(false); if (device == null) return; if (device.isDisposed()) return; destroy(); @@ -165,29 +162,29 @@ public Device getDevice() { void ignoreNonDisposed() { if (tracker != null) { - tracker.ignoreMe = true; + tracker.reporting.set(false); } } void init() { if (device.tracking) device.new_Object(this); + if (tracker != null && tracker.reporting.compareAndSet(false, true)) { + cleaner.register(this, tracker); + } } void initNonDisposeTracking() { // Color doesn't really have any resource to be leaked, ignore. if (this instanceof Color) return; - // Avoid performance costs of having '.finalize()' when not tracking. + // Avoid performance costs of gathering the current stack trace when not tracking. if (nonDisposedReporter == null) return; // Capture a stack trace to help investigating the leak Error error = new Error("SWT Resource was not properly disposed"); //$NON-NLS-1$ - // Allocate a helper class with '.finalize()' in it, it will do the actual - // work of detecting and reporting errors. This works because Resource - // holds the only reference to 'ResourceTracker' and therefore the tracker - // is only GC'ed when Resource itself is ready to be GC'ed. - tracker = new ResourceTracker(this, error); + // Create the tracker that will later be registered as a cleaning action for this resource. + tracker = new ResourceTracker(error); } /**