aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/share/classes/sun/awt/SunDisplayChanger.java84
-rw-r--r--test/java/awt/Toolkit/SunDisplayChangerLeakTest/SunDisplayChangerLeakTest.java117
2 files changed, 59 insertions, 142 deletions
diff --git a/src/share/classes/sun/awt/SunDisplayChanger.java b/src/share/classes/sun/awt/SunDisplayChanger.java
index 702fbe5b89..27fe0f8ee7 100644
--- a/src/share/classes/sun/awt/SunDisplayChanger.java
+++ b/src/share/classes/sun/awt/SunDisplayChanger.java
@@ -26,8 +26,13 @@
package sun.awt;
import java.awt.IllegalComponentStateException;
-import java.util.*;
-import java.util.function.Consumer;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
import sun.util.logging.PlatformLogger;
@@ -100,7 +105,40 @@ public class SunDisplayChanger {
if (log.isLoggable(PlatformLogger.Level.FINEST)) {
log.finest("notifyListeners");
}
- notifyChanged(DisplayChangedListener::displayChanged, "displayChanged");
+ // This method is implemented by making a clone of the set of listeners,
+ // and then iterating over the clone. This is because during the course
+ // of responding to a display change, it may be appropriate for a
+ // DisplayChangedListener to add or remove itself from a SunDisplayChanger.
+ // If the set itself were iterated over, rather than a clone, it is
+ // trivial to get a ConcurrentModificationException by having a
+ // DisplayChangedListener remove itself from its list.
+ // Because all display change handling is done on the event thread,
+ // synchronization provides no protection against modifying the listener
+ // list while in the middle of iterating over it. -bchristi 7/10/2001
+
+ Set<DisplayChangedListener> cloneSet;
+
+ synchronized(listeners) {
+ cloneSet = new HashSet<DisplayChangedListener>(listeners.keySet());
+ }
+
+ Iterator<DisplayChangedListener> itr = cloneSet.iterator();
+ while (itr.hasNext()) {
+ DisplayChangedListener current = itr.next();
+ try {
+ if (log.isLoggable(PlatformLogger.Level.FINEST)) {
+ log.finest("displayChanged for listener: " + current);
+ }
+ current.displayChanged();
+ } catch (IllegalComponentStateException e) {
+ // This DisplayChangeListener is no longer valid. Most
+ // likely, a top-level window was dispose()d, but its
+ // Java objects have not yet been garbage collected. In any
+ // case, we no longer need to track this listener, though we
+ // do need to remove it from the original list, not the clone.
+ listeners.remove(current);
+ }
+ }
}
/*
@@ -111,34 +149,30 @@ public class SunDisplayChanger {
if (log.isLoggable(PlatformLogger.Level.FINEST)) {
log.finest("notifyPaletteChanged");
}
- notifyChanged(DisplayChangedListener::paletteChanged, "paletteChanged");
- }
+ // This method is implemented by making a clone of the set of listeners,
+ // and then iterating over the clone. This is because during the course
+ // of responding to a display change, it may be appropriate for a
+ // DisplayChangedListener to add or remove itself from a SunDisplayChanger.
+ // If the set itself were iterated over, rather than a clone, it is
+ // trivial to get a ConcurrentModificationException by having a
+ // DisplayChangedListener remove itself from its list.
+ // Because all display change handling is done on the event thread,
+ // synchronization provides no protection against modifying the listener
+ // list while in the middle of iterating over it. -bchristi 7/10/2001
- private void notifyChanged(Consumer<DisplayChangedListener> callback, String callbackName) {
- // This method is implemented by making a clone of the set of listeners,
- // and then iterating over the clone. This is because during the course
- // of responding to a display change, it may be appropriate for a
- // DisplayChangedListener to add or remove itself from a SunDisplayChanger.
- // If the set itself were iterated over, rather than a clone, it is
- // trivial to get a ConcurrentModificationException by having a
- // DisplayChangedListener remove itself from its list.
- // Because all display change handling is done on the event thread,
- // synchronization provides no protection against modifying the listener
- // list while in the middle of iterating over it. -bchristi 7/10/2001
-
- // Preserve "weakness" of the original map to avoid OOME.
- WeakHashMap<DisplayChangedListener, Void> cloneMap;
+ Set<DisplayChangedListener> cloneSet;
- synchronized(listeners) {
- cloneMap = new WeakHashMap<>(listeners);
+ synchronized (listeners) {
+ cloneSet = new HashSet<DisplayChangedListener>(listeners.keySet());
}
-
- for (DisplayChangedListener current : cloneMap.keySet()) {
+ Iterator<DisplayChangedListener> itr = cloneSet.iterator();
+ while (itr.hasNext()) {
+ DisplayChangedListener current = itr.next();
try {
if (log.isLoggable(PlatformLogger.Level.FINEST)) {
- log.finest(callbackName + " for listener: " + current);
+ log.finest("paletteChanged for listener: " + current);
}
- callback.accept(current);
+ current.paletteChanged();
} catch (IllegalComponentStateException e) {
// This DisplayChangeListener is no longer valid. Most
// likely, a top-level window was dispose()d, but its
diff --git a/test/java/awt/Toolkit/SunDisplayChangerLeakTest/SunDisplayChangerLeakTest.java b/test/java/awt/Toolkit/SunDisplayChangerLeakTest/SunDisplayChangerLeakTest.java
deleted file mode 100644
index 4022b499f9..0000000000
--- a/test/java/awt/Toolkit/SunDisplayChangerLeakTest/SunDisplayChangerLeakTest.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2017 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import sun.awt.DisplayChangedListener;
-import sun.awt.image.BufferedImageGraphicsConfig;
-import sun.awt.image.SunVolatileImage;
-import sun.java2d.SunGraphicsEnvironment;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.image.BufferedImage;
-import java.awt.image.VolatileImage;
-import java.util.*;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-
-/*
- https://youtrack.jetbrains.com/issue/JRE-577
- @test
- @summary SunDisplayChanger should not prevent listeners from gc'ing
- @author anton.tarasov
- @run main/othervm -Xmx64m -Dswing.bufferPerWindow=true SunDisplayChangerLeakTest
-*/
-public class SunDisplayChangerLeakTest {
- static int frameCountDown = 20;
- static final Map<Image, Void> STRONG_MAP = new HashMap<>();
- static final GraphicsConfiguration BI_GC = BufferedImageGraphicsConfig.getConfig(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB));
-
- static volatile boolean passed;
-
- /**
- * Shows a frame, then refs its back buffer.
- */
- static void showFrame(CountDownLatch latch) {
- JFrame frame = new JFrame("frame") {
- @Override
- public VolatileImage createVolatileImage(int w, int h) {
- // Back the frame buffer by BufferedImage so that it's allocated in RAM
- VolatileImage img = new SunVolatileImage(BI_GC, w, h, Transparency.TRANSLUCENT, new ImageCapabilities(false));
- STRONG_MAP.put(img, null);
-
- EventQueue.invokeLater(() -> {
- dispose(); // release the frame buffer
- latch.countDown();
- });
- return img;
- }
- };
- frame.setSize(500, 500);
- frame.setLocationRelativeTo(null);
- frame.setVisible(true);
- }
-
- public static void main(String[] args) throws InterruptedException {
- while(--frameCountDown >= 0) {
- CountDownLatch latch = new CountDownLatch(1);
- EventQueue.invokeLater(() -> showFrame(latch));
- latch.await();
- }
-
- SunGraphicsEnvironment env = (SunGraphicsEnvironment)SunGraphicsEnvironment.getLocalGraphicsEnvironment();
- DisplayChangedListener strongRef;
- env.addDisplayChangedListener(strongRef = new DisplayChangedListener() {
- @Override
- public void displayChanged() {
- // Now we're in the process of iterating over a local copy of the DisplayChangedListener's internal map.
- // Let's force OOME and make sure the local copy doesn't prevent the listeners from gc'ing.
-
- int strongSize = STRONG_MAP.size();
- System.out.println("strong size: " + strongSize);
-
- // Release the images
- Map<Image, Void> weakMap = new WeakHashMap<>(STRONG_MAP);
- STRONG_MAP.clear();
-
- List<Object> garbage = new ArrayList<>();
- try {
- while (true) {
- garbage.add(new int[1000000]);
- }
- } catch (OutOfMemoryError e) {
- garbage.clear();
- System.out.println("OutOfMemoryError");
- }
- int weakSize = weakMap.size();
- System.out.println("weak size: " + weakSize);
-
- passed = weakSize < strongSize;
- }
-
- @Override
- public void paletteChanged() {
- System.out.println(new int[1000]);
- }
- });
- assert strongRef != null; // make it "used" to please javac
-
- // call the above listener
- env.displayChanged();
-
- if (!passed) throw new RuntimeException("Test FAILED");
-
- System.out.println("Test PASSED");
- }
-}