【问题标题】:Mac OS Java 7 JDialog.dispose Memory leakMac OS Java 7 JDialog.dispose 内存泄漏
【发布时间】:2013-11-15 21:46:21
【问题描述】:

在调用 JDialog.dispose 来处理 JDialog 时,我观察到 OS 和 Java 版本之间存在一些不一致的行为(JFrame 也会出现这种情况)。

下面的简单示例应用程序可用于演示该问题。如果您运行它并分析应用程序,您会注意到通过单击“新对话框”创建并随后关闭的任何 JDialog 实例都不会被垃圾收集,因为它们仍被 sun.lwawt.macosx.CPlatformWindow 的实例引用,从而导致内存泄漏应用程序。

我不相信这是由于任何弱引用,因为我在经历过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 错误报告(我没有运气找到)?

有什么建议的解决方法吗?

【问题讨论】:

  • 检查了一个相关的例子here。
  • @trashgod 谢谢,我在调查期间确实看到了这一点。但是,我没有看到它是如何直接应用在这里的,因为在以前的 Java 版本和不同的操作系统中,在完全相同的代码下,我会看到不同的行为。
  • 可能是延迟或错误,但另请参阅answer。
  • 最近的 JDK 中修复了这个错误吗?这太老了,从那以后已经发布了很多版本。 openjdk 错误跟踪器显示它仍然处于打开状态......经过近 8 年。

标签: java macos swing memory-leaks awt


【解决方案1】:

我看到了同样的东西,并且能够通过覆盖我的窗口上的 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,但总比没有好,应该对您有所帮助。

【讨论】:

    【解决方案2】:

    我使用以下代码来尝试最小化内存泄漏。仍然会有垃圾收集器未收集的资源,但是作为 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());
                }
            }
    

    【讨论】:

      猜你喜欢
      • 2013-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-21
      相关资源
      最近更新 更多