Skip to content

Commit accaa36

Browse files
mkartashevjbrbot
authored andcommitted
JBR-9893 Closed and disposed windows leak through WLGraphicsDevice.toplevels
1 parent 3386881 commit accaa36

File tree

4 files changed

+154
-12
lines changed

4 files changed

+154
-12
lines changed

src/java.desktop/unix/classes/sun/awt/wl/WLGraphicsDevice.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@
3535
import java.awt.Insets;
3636
import java.awt.Rectangle;
3737
import java.awt.Window;
38+
import java.lang.ref.WeakReference;
39+
import java.util.ArrayList;
3840
import java.util.HashSet;
41+
import java.util.List;
3942
import java.util.Set;
4043

4144
/**
@@ -119,7 +122,7 @@ public class WLGraphicsDevice extends GraphicsDevice {
119122
* Top-level window peers that consider this device as their primary one
120123
* and get their graphics configuration from it
121124
*/
122-
private final Set<WLComponentPeer> toplevels = new HashSet<>(); // guarded by 'this'
125+
private final Set<WeakReference<WLComponentPeer>> toplevels = new HashSet<>(); // guarded by 'this'
123126

124127
private WLGraphicsDevice(int id,
125128
String name,
@@ -205,9 +208,14 @@ void updateConfiguration(String name,
205208
}
206209

207210
private void notifyToplevels() {
208-
Set<WLComponentPeer> toplevelsCopy = new HashSet<>(toplevels.size());
211+
List<WLComponentPeer> toplevelsCopy = new ArrayList<>(toplevels.size());
209212
synchronized (this) {
210-
toplevelsCopy.addAll(toplevels);
213+
for (var toplevel: toplevels) {
214+
WLComponentPeer peer = toplevel.get();
215+
if (peer != null) {
216+
toplevelsCopy.add(peer);
217+
}
218+
}
211219
}
212220
toplevelsCopy.forEach(WLComponentPeer::checkIfOnNewScreen);
213221
}
@@ -369,13 +377,16 @@ private void exitFullScreenExclusive(Window w) {
369377

370378
public void addWindow(WLComponentPeer peer) {
371379
synchronized (this) {
372-
toplevels.add(peer);
380+
toplevels.add(new WeakReference<>(peer));
373381
}
374382
}
375383

376384
public void removeWindow(WLComponentPeer peer) {
377385
synchronized (this) {
378-
toplevels.remove(peer);
386+
toplevels.removeIf(ref -> {
387+
WLComponentPeer p = ref.get();
388+
return p == null || p == peer;
389+
});
379390
}
380391
}
381392

src/java.desktop/unix/classes/sun/awt/wl/WLKeyboardFocusManagerPeer.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66

77
import java.awt.Component;
88
import java.awt.Window;
9+
import java.lang.ref.WeakReference;
910

1011
public class WLKeyboardFocusManagerPeer extends KeyboardFocusManagerPeerImpl {
1112
private static final PlatformLogger focusLog = PlatformLogger.getLogger("sun.awt.wl.focus.WLKeyboardFocusManagerPeer");
1213

13-
private Window currentFocusedWindow;
14+
private WeakReference<Window> currentFocusedWindow = new WeakReference<>(null);
1415
private static final WLKeyboardFocusManagerPeer instance = new WLKeyboardFocusManagerPeer();
1516

1617
public static WLKeyboardFocusManagerPeer getInstance() {
@@ -23,14 +24,14 @@ public void setCurrentFocusedWindow(Window win) {
2324
if (focusLog.isLoggable(PlatformLogger.Level.FINER)) {
2425
focusLog.finer("Current focused window -> " + win);
2526
}
26-
currentFocusedWindow = win;
27+
currentFocusedWindow = new WeakReference<>(win);
2728
}
2829
}
2930

3031
@Override
3132
public Window getCurrentFocusedWindow() {
3233
synchronized (this) {
33-
return currentFocusedWindow;
34+
return currentFocusedWindow.get();
3435
}
3536
}
3637

@@ -61,7 +62,7 @@ public void setCurrentFocusOwner(Component comp) {
6162
@Override
6263
public Component getCurrentFocusOwner() {
6364
synchronized (this) {
64-
return currentFocusedWindow;
65+
return currentFocusedWindow.get();
6566
}
6667
}
6768
}

src/java.desktop/unix/classes/sun/awt/wl/WLToolkit.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@
8585
import java.security.PrivilegedAction;
8686
import java.util.ArrayList;
8787
import java.util.Arrays;
88-
import java.util.ArrayList;
8988
import java.util.Collections;
9089
import java.util.HashMap;
9190
import java.util.Map;
@@ -471,12 +470,13 @@ private static void dispatchKeyboardLeaveEvent(long serial, long surfacePtr) {
471470

472471
keyboard.onLostFocus();
473472

473+
WLKeyboardFocusManagerPeer.getInstance().setCurrentFocusedWindow(null);
474+
WLKeyboardFocusManagerPeer.getInstance().setCurrentFocusOwner(null);
475+
474476
final WLInputState newInputState = inputState.updatedFromKeyboardLeaveEvent(serial, surfacePtr);
475477
final WLWindowPeer peer = peerFromSurface(surfacePtr);
476478
if (peer != null && peer.getTarget() instanceof Window window) {
477479
final WindowEvent winLostFocusEvent = new WindowEvent(window, WindowEvent.WINDOW_LOST_FOCUS);
478-
WLKeyboardFocusManagerPeer.getInstance().setCurrentFocusedWindow(null);
479-
WLKeyboardFocusManagerPeer.getInstance().setCurrentFocusOwner(null);
480480
postPriorityEvent(winLostFocusEvent);
481481
}
482482
inputState = newInputState;
@@ -1116,6 +1116,19 @@ public static boolean isSSDAvailable() {
11161116

11171117
protected static void targetDisposedPeer(Object target, Object peer) {
11181118
SunToolkit.targetDisposedPeer(target, peer);
1119+
if (target instanceof Window window) {
1120+
// When a window is disposed, we may not get the keyboard leave event that
1121+
// normally posts WINDOW_LOST_FOCUS, so we need to post it manually to maintain
1122+
// a correct state of the keyboard focus manager.
1123+
final WindowEvent winLostFocusEvent = new WindowEvent(window, WindowEvent.WINDOW_LOST_FOCUS);
1124+
postPriorityEvent(winLostFocusEvent);
1125+
1126+
var gc = window.getGraphicsConfiguration();
1127+
if (gc != null && peer instanceof WLWindowPeer windowPeer) {
1128+
WLGraphicsDevice gd = (WLGraphicsDevice) gc.getDevice();
1129+
gd.removeWindow(windowPeer);
1130+
}
1131+
}
11191132
}
11201133

11211134
static void postEvent(AWTEvent event) {
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright 2026 JetBrains s.r.o.
3+
* Copyright (c) 2026, 2016, Oracle and/or its affiliates. All rights reserved.
4+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5+
*
6+
* This code is free software; you can redistribute it and/or modify it
7+
* under the terms of the GNU General Public License version 2 only, as
8+
* published by the Free Software Foundation.
9+
*
10+
* This code is distributed in the hope that it will be useful, but WITHOUT
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13+
* version 2 for more details (a copy is included in the LICENSE file that
14+
* accompanied this code).
15+
*
16+
* You should have received a copy of the GNU General Public License version
17+
* 2 along with this work; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19+
*
20+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
21+
* or visit www.oracle.com if you need additional information or have any
22+
* questions.
23+
*/
24+
25+
/*
26+
* @test
27+
* @key headful
28+
* @summary Tests that nothing prevents windows from being removed from the global list
29+
* after they have been disposed
30+
* @library /javax/swing/regtesthelpers
31+
* @modules java.desktop/sun.awt
32+
* java.desktop/sun.java2d
33+
* @build Util
34+
* @run main/othervm -Xms32M -Xmx32M WLWindowsLeak
35+
*/
36+
37+
import java.awt.Color;
38+
import java.awt.Frame;
39+
import java.awt.Robot;
40+
import java.awt.Window;
41+
import java.io.IOException;
42+
import java.lang.management.ManagementFactory;
43+
import java.lang.ref.WeakReference;
44+
import java.util.ArrayList;
45+
import java.util.List;
46+
import java.util.Vector;
47+
48+
import com.sun.management.HotSpotDiagnosticMXBean;
49+
import sun.awt.AppContext;
50+
import sun.java2d.Disposer;
51+
52+
public class WLWindowsLeak {
53+
54+
public static final int WINDOWS_COUNT = 100;
55+
private static volatile boolean disposerPhantomComplete;
56+
57+
public static void main(String[] args) throws Exception {
58+
spawnWindows();
59+
60+
Disposer.addRecord(new Object(), () -> disposerPhantomComplete = true);
61+
62+
while (!disposerPhantomComplete) {
63+
Util.generateOOME();
64+
}
65+
66+
Vector<WeakReference<Window>> windowList =
67+
(Vector<WeakReference<Window>>) AppContext.getAppContext().get(Window.class);
68+
69+
if (windowList != null && !windowList.isEmpty()) {
70+
dumpHeap("heap_dump_live_windows.hprof");
71+
System.out.println("Live window list:");
72+
windowList.forEach(ref -> System.out.println(ref.get()));
73+
throw new RuntimeException("Test FAILED: Window list is not empty: " + windowList.size());
74+
}
75+
76+
System.out.println("Test PASSED");
77+
}
78+
79+
private static void dumpHeap(String filePath) {
80+
try {
81+
HotSpotDiagnosticMXBean mxBean = ManagementFactory
82+
.getPlatformMXBean(HotSpotDiagnosticMXBean.class);
83+
mxBean.dumpHeap(filePath, true); // true = live objects only
84+
System.out.println("Heap dump created at: " + filePath);
85+
} catch (IOException e) {
86+
System.err.println("Failed to dump heap: " + e.getMessage());
87+
}
88+
}
89+
90+
private static void spawnWindows() throws Exception {
91+
List<WeakReference<Frame>> frameList = new ArrayList<>();
92+
Robot r = new Robot();
93+
94+
for (int i = 0; i < WINDOWS_COUNT; i++) {
95+
Frame f = new Frame(String.format("Frame %d", i));
96+
f.setBackground(Color.WHITE);
97+
f.setSize(100, 100);
98+
f.setVisible(true);
99+
frameList.add(new WeakReference<>(f));
100+
Thread.sleep(42);
101+
}
102+
r.waitForIdle();
103+
104+
frameList.forEach(ref -> {
105+
ref.get().setVisible(false);
106+
});
107+
r.waitForIdle();
108+
109+
frameList.forEach(ref -> {
110+
var f = ref.get();
111+
if (f != null) f.dispose();
112+
});
113+
frameList.clear();
114+
r.waitForIdle(); // to make sure no events hold a reference to frames
115+
System.gc();
116+
}
117+
}

0 commit comments

Comments
 (0)