Skip to content

Commit c7712e4

Browse files
otbutziloveeclipse
authored andcommitted
Replace finalize() with a Cleaner
1 parent f35ee7f commit c7712e4

File tree

1 file changed

+51
-26
lines changed
  • bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics

1 file changed

+51
-26
lines changed

bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/Resource.java

+51-26
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
*******************************************************************************/
1414
package org.eclipse.swt.graphics;
1515

16+
import java.lang.ref.*;
17+
import java.lang.ref.Cleaner.*;
1618
import java.util.*;
19+
import java.util.concurrent.*;
20+
import java.util.concurrent.atomic.*;
1721
import java.util.function.*;
1822

1923
import org.eclipse.swt.*;
@@ -44,41 +48,35 @@
4448
public abstract class Resource {
4549

4650
/**
47-
* Used to track not disposed SWT resource. A separate class allows
48-
* not to have the {@link #finalize} when tracking is disabled, avoiding
49-
* possible performance issues in GC.
51+
* Used to track not disposed SWT resource.
5052
*/
51-
private static class ResourceTracker {
53+
private static final class ResourceTracker implements Runnable {
5254
/**
53-
* Resource that is tracked here
55+
* Invokes {@link #run()} once the resource is eligible for GC
5456
*/
55-
private Resource resource;
57+
private static final Cleaner cleaner = Cleaner.create(new ResourceTrackerThreadFactory());
5658

5759
/**
5860
* Recorded at Resource creation if {@link #setNonDisposeHandler} was
5961
* enabled, used to track resource disposal
6062
*/
61-
private Error allocationStack;
63+
private final Error allocationStack;
6264

6365
/**
64-
* Allows to ignore specific Resources even if they are not disposed
65-
* properly, used for example for Fonts that SWT doesn't own.
66+
* Controls whether the {@link Resource#nonDisposedReporter} should be notified
6667
*/
67-
boolean ignoreMe;
68+
private final AtomicBoolean reporting = new AtomicBoolean(false);
6869

69-
ResourceTracker(Resource resource, Error allocationStack) {
70-
this.resource = resource;
70+
ResourceTracker(Error allocationStack) {
7171
this.allocationStack = allocationStack;
7272
}
7373

7474
@Override
75-
protected void finalize() {
76-
if (ignoreMe) return;
75+
public void run() {
76+
if (!reporting.get()) return;
7777
if (nonDisposedReporter == null) return;
7878

79-
// If the Resource is GC'ed before it was disposed, this is a leak.
80-
if (!resource.isDisposed())
81-
nonDisposedReporter.accept(allocationStack);
79+
nonDisposedReporter.accept(allocationStack);
8280
}
8381
}
8482

@@ -97,6 +95,11 @@ protected void finalize() {
9795
*/
9896
private ResourceTracker tracker;
9997

98+
/**
99+
* Represents the {@link #tracker} registered as a cleaning action via the {@link ResourceTracker#cleaner}
100+
*/
101+
private Cleanable cleanable;
102+
100103
static {
101104
boolean trackingEnabled = Boolean.getBoolean("org.eclipse.swt.graphics.Resource.reportNonDisposed"); //$NON-NLS-1$
102105
if (trackingEnabled) {
@@ -142,6 +145,8 @@ void destroyHandlesExcept(Set<Integer> zoomLevels) {
142145
* This method does nothing if the resource is already disposed.
143146
*/
144147
public void dispose() {
148+
if (tracker != null) tracker.reporting.set(false);
149+
if (cleanable != null) cleanable.clean();
145150
if (device == null) return;
146151
if (device.isDisposed()) return;
147152
destroy();
@@ -164,30 +169,29 @@ public Device getDevice() {
164169
}
165170

166171
void ignoreNonDisposed() {
167-
if (tracker != null) {
168-
tracker.ignoreMe = true;
169-
}
172+
if (tracker != null) tracker.reporting.set(false);
173+
if (cleanable != null) cleanable.clean();
170174
}
171175

172176
void init() {
173177
if (device.tracking) device.new_Object(this);
178+
if (tracker != null && tracker.reporting.compareAndSet(false, true)) {
179+
cleanable = ResourceTracker.cleaner.register(this, tracker);
180+
}
174181
}
175182

176183
void initNonDisposeTracking() {
177184
// Color doesn't really have any resource to be leaked, ignore.
178185
if (this instanceof Color) return;
179186

180-
// Avoid performance costs of having '.finalize()' when not tracking.
187+
// Avoid performance costs of gathering the current stack trace when not tracking.
181188
if (nonDisposedReporter == null) return;
182189

183190
// Capture a stack trace to help investigating the leak
184191
Error error = new Error("SWT Resource was not properly disposed"); //$NON-NLS-1$
185192

186-
// Allocate a helper class with '.finalize()' in it, it will do the actual
187-
// work of detecting and reporting errors. This works because Resource
188-
// holds the only reference to 'ResourceTracker' and therefore the tracker
189-
// is only GC'ed when Resource itself is ready to be GC'ed.
190-
tracker = new ResourceTracker(this, error);
193+
// Create the tracker that will later be registered as a cleaning action for this resource.
194+
tracker = new ResourceTracker(error);
191195
}
192196

193197
/**
@@ -220,4 +224,25 @@ public static void setNonDisposeHandler(Consumer<Error> reporter) {
220224
nonDisposedReporter = reporter;
221225
}
222226

227+
private final static class ResourceTrackerThreadFactory implements ThreadFactory {
228+
229+
private final ThreadGroup group;
230+
231+
public ResourceTrackerThreadFactory() {
232+
ThreadGroup root = Thread.currentThread().getThreadGroup();
233+
while (root.getParent() != null) {
234+
root = root.getParent();
235+
}
236+
this.group = new ThreadGroup(root, "SWTResourceTrackerThreadGroup"); //$NON-NLS-1$
237+
}
238+
239+
@Override
240+
public Thread newThread(Runnable r) {
241+
Thread thread = new Thread(group, r, "SWTResourceTracker", 0, false); //$NON-NLS-1$
242+
thread.setPriority(Thread.MAX_PRIORITY - 2);
243+
thread.setContextClassLoader(ClassLoader.getSystemClassLoader());
244+
return thread;
245+
}
246+
}
247+
223248
}

0 commit comments

Comments
 (0)