Anton:
I am very grateful that you checked out the sample code, but I have to say you freaked me out
a bit with the memory leak in Swing guess. So, I redoubled my efforts to check on your observations. I have rewritten the test to perhaps flush out as much confusion as possible. Any comments to further hone in on the problem or remove confusion would be most welcome! What I have added to the test is a
???GC??™ button to the right within the primary frame. This offers the ability minimize any affects caused by a simple window focus change to an attached YourKit to perform that task. The
???GC??™ button also saves a YourKit memory snap after a short delay after the GC. Prior to running GC, I null out all the
nooks and crannies within Swing where strong references can misguide those searching for
real memory leaks.
Stdout will show when each prior launched dialog (
SuperSimpleApp$FinalizeReportDialog) is finialized and will report any lingering dialogs still weakly referenced within the frame??™s
ownedWindows. If your press
'Launch Dialog' 3x; I expect you to see 3 dialogs finalized when you press
'GC'.
Try using this program on the command line (remember to include the yjpagent). I think you will find that for every time that you have pressed
???Launch Dialog??™, a press of
???GC??™ will yield as many finalizations of dialogs and an empty list of weakly referenced
ownedWindows. I and my fellow Eclipser are using JDK 1.5.0_06. Additionally, I have used 1.5.0_9 with the same observation. Not until I Run or Debug or even Profile via YourKit plugin from IDEA do they not clean up. The memory snap points out that all
SuperSimpleApp$FinalizeReportDialog dialogs are indeed
JNI Globals.
This seems to indicate to me that it isn??™t the JDK, but some relationship with IDEA 6.0 that causes the weirdness. Memory leaks within Swing??™s history are well documented in the Bug Parade, however a memory leak at this stage of the maturity of 1.5 for such a simple and oft used scenario as exercised in my example would really surprise me. I know you guys have history with the JB dudes so I am hoping that you could share your observations with them perhaps and see what they make of it? If not, I understand and will take it up with them...
Performing the test using JDK 6.0 shows that all
SuperSimpleApp$FinalizeReportDialog dialogs are finalized with the exception of the one being held by the
RepaintManager. This happens on the command line or using IDEA. Okay, a memory leak in Swing for an oft used feature, but it iiiiis still beta...
My Eclipser won??™t be able to retest my juiced up sample until tomorrow, but I will be surprised if she finds an Eclipse debug session creating unrecoverable
JNI Global anchored dialogs...
Wish there was a better way to share code with you...
Code: Select all
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.*;
import javax.swing.*;
import com.yourkit.api.Controller;
public class SuperSimpleApp extends JFrame {
public SuperSimpleApp(String title) throws . {
super(title);
getContentPane().add(new JButton(new AbstractAction("Launch Dialog") {
public void actionPerformed(ActionEvent e) {
new FinalizeReportDialog().setVisible(true);
}
}));
getContentPane().add(new JButton(new AbstractAction("GC") {
public void actionPerformed(ActionEvent ae) {
// Clear out the Swing internals that could be holding onto
// references...
SuperSimpleApp.referenceSmackDown();
System.gc();
final Timer t = new Timer(500, new SnapMemoryAndReportOwnedDialogs());
t.setRepeats(false);
t.start();
}
}), BorderLayout.EAST);
}
// Get agresssive with removing every possible reference to the
// FinalizeReportDialog...
private static void referenceSmackDown() {
try {
final Field newFocusOwner = KeyboardFocusManager.class.getDeclaredField("newFocusOwner");
newFocusOwner.setAccessible(true);
if (newFocusOwner.get(null) instanceof FinalizeReportDialog) {
System.out.println("Smack newFocusOwner...");
newFocusOwner.set(null, null);
}
final Field realOppositeWindow = DefaultKeyboardFocusManager.class.getDeclaredField("realOppositeWindow");
realOppositeWindow.setAccessible(true);
if (realOppositeWindow.get(KeyboardFocusManager.getCurrentKeyboardFocusManager()) instanceof FinalizeReportDialog) {
System.out.println("Smack realOppositeWindow...");
realOppositeWindow.set(KeyboardFocusManager.getCurrentKeyboardFocusManager(), null);
}
final Method getTemporaryLostComponentMethod =
Window.class.getDeclaredMethod("getTemporaryLostComponent");
getTemporaryLostComponentMethod.setAccessible(true);
final Method setTemporaryLostComponentMethod =
Window.class.getDeclaredMethod("setTemporaryLostComponent", Component.class);
setTemporaryLostComponentMethod.setAccessible(true);
final Object[] nullAry = new Object[]{null};
for (Frame frame : Frame.getFrames()) {
if (getTemporaryLostComponentMethod.invoke(frame) instanceof FinalizeReportDialog) {
System.out.println("Setting Frame temporaryLostComponent...");
setTemporaryLostComponentMethod.invoke(frame, nullAry);
}
for (Window ownedWindow : frame.getOwnedWindows()) {
if (getTemporaryLostComponentMethod.invoke(ownedWindow) instanceof FinalizeReportDialog) {
System.out.println("Setting Frame's owned window temporaryLostComponent...");
setTemporaryLostComponentMethod.invoke(ownedWindow, nullAry);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private class FinalizeReportDialog extends JDialog {
public FinalizeReportDialog() throws . {
super(SuperSimpleApp.this, "Dialog", true);
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
getContentPane().add(new JLabel("Dismiss with red 'X'"));
pack();
}
protected void finalize() throws Throwable {
super.finalize();
System.out.println("Dialog Finalized. Yahoo!");
}
}
private class SnapMemoryAndReportOwnedDialogs implements ActionListener {
public void actionPerformed(ActionEvent ae) {
try {
new Controller().captureMemorySnapshot();
System.out.println("Frame owned dialogs: ");
for (Window window : SuperSimpleApp.this.getOwnedWindows())
System.out.println('\t' + window.getClass().getName() +
"@" + Integer.toHexString(window.hashCode()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
EventQueue.invokeAndWait(new Runnable() {
public void run() {
final SuperSimpleApp simp = new SuperSimpleApp("Super Simple");
simp.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
simp.pack();
simp.setVisible(true);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}