Mac OS上Java 7中JDialog.dispose存在内存泄漏问题。
Mac OS上Java 7中JDialog.dispose存在内存泄漏问题。
我在调用JDialog.dispose释放JDialog(也适用于JFrame)时,观察到操作系统和Java版本之间的一些不一致的行为。
下面的简单示例应用程序可以用于演示问题。如果运行它并对应用程序进行分析,您将注意到通过单击“新对话框”创建并随后关闭的任何JDialog实例都不会被垃圾收集,因为它们仍由sun.lwawt.macosx.CPlatformWindow的实例引用,从而在应用程序中造成内存泄漏。
我不认为这是由于任何weak references,因为我在一个经历过OutOfMemoryError的环境中观察到了这个问题,所以我预计在那时可以被垃圾收集的任何东西都应该被垃圾收集。
问题发生在以下环境中:
- Mac OS X 10.9:Java 1.7.0_5
- Mac OS X 10.9:Java 1.7.0_45
问题在以下环境中不会发生:
- Mac OS X 10.9:Java 1.6.0_65
- Windows 7:Java 1.7.0_45
在这些环境中,JDialog实例会迅速收集,并且(显然)在JProfiler中不再可见。
注意:问题使用DISPOSE_ON_CLOSE或手动处理关闭(在示例中已注释)发生。
import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.*; public class Testing extends JFrame { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { final JDialog parent = new JDialog((Frame)null, "Parent", false); JButton add = new JButton("New Dialog"); add.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final JDialog child = new JDialog(parent, "Child", false); // child.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); child.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); child.setSize(100, 100); //child.addWindowListener(new WindowAdapter() { // @Override // public void windowClosing(WindowEvent e) { // child.setVisible(false); // child.dispose(); // } //}); child.setVisible(true); } }); parent.add(add); parent.pack(); parent.setVisible(true); } }); } }
我做错了什么吗?
我的期望行为不正确吗?
如果不是,有谁能指导我提供Java错误报告(我找不到任何一篇)?
有什么建议的解决方法吗?
我使用以下代码来尝试最小化内存泄漏。垃圾回收器仍将有资源未被回收,但是所有作为JFrame或JDialog子组件的Swing组件都将被垃圾回收。可以使用较短的标题(或无标题)来使占用空间更小。我保持了一个有意义的标题,以便在必要时更轻松地在分析器中跟踪事物。使用此代码后,我的应用程序的内存占用足够小,可长时间运行并且可以频繁打开和关闭窗口。如果没有此代码,则某些用户在打开应用程序数天后,使用某些重量级窗口进行几十次打开和关闭操作后,内存将耗尽。
protected void disposeAndEmptyOnClose(Component c) { if ( c instanceof JFrame ) { JFrame frame = (JFrame) c; if (!frame.getClass().isAssignableFrom(JFrame.class)) { LOG.warn("potential memory leak. Cannot guarantee memory is freed after frame is disposed because" + " JFrame has been subclassed to " + frame.getClass().getName()); } frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.addWindowListener(new WindowAdapter() { @Override public void windowClosed(WindowEvent e) { frame.removeAll(); frame.setContentPane(new JPanel()); frame.setJMenuBar(null); frame.removeWindowListener(this); frame.setTitle("disposed and emptied: "+frame.getTitle()); } }); } else if ( c instanceof JDialog ) { JDialog dialog = (JDialog)c; if (!dialog.getClass().isAssignableFrom(JDialog.class)) { LOG.warn("potential memory leak. Cannot guarantee memory is freed after dialog is disposed " + "because JDialog has been subclassed to " + dialog.getClass().getName()); } dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); dialog.addWindowListener(new WindowAdapter() { @Override public void windowClosed(WindowEvent e) { dialog.removeAll(); dialog.setContentPane(new JPanel()); dialog.removeWindowListener(this); dialog.setTitle("disposed and emptied: "+dialog.getTitle()); } }); } else { LOG.warn("disposeAndEmptyOnClose not supported for " + c.getClass().getSimpleName()); } }
我也遇到了同样的问题,并且通过在我的窗口上覆盖dispose方法来释放窗口,像这样:
@SuppressWarnings("deprecation") @Override public void dispose() { ComponentPeer peer = getPeer(); super.dispose(); if (null != peer) { try { Class> peerClass = Class.forName("sun.lwawt.LWComponentPeer"); Field targetField = peerClass.getDeclaredField("target"); targetField.setAccessible(true); targetField.set(peer, null); Field windowField = peer.getClass().getDeclaredField("platformWindow"); windowField.setAccessible(true); Object platformWindow = windowField.get(peer); targetField = platformWindow.getClass().getDeclaredField("target"); targetField.setAccessible(true); targetField.set(platformWindow, null); Field componentField = peerClass.getDeclaredField("platformComponent"); componentField.setAccessible(true); Object platformComponent = componentField.get(peer); targetField = platformComponent.getClass().getDeclaredField("target"); targetField.setAccessible(true); targetField.set(platformComponent, null); } catch (Exception e) { e.printStackTrace(); } } }
虽然这不能释放CPlatformWindow,但好过没有,并且应该能帮到你。