JFrame 永远不会被垃圾回收。
JFrame 永远不会被垃圾回收。
我有一个应用程序需要打开多个JFrames(这是一个日志查看器,有时需要在单独的窗口中查看一堆日志以进行比较)。
看起来JVM(Java 8更新101在OS X上)持有JFrame的强引用,这会阻止它被垃圾回收,并最终导致抛出OutOfMemoryError。
为了看到问题,使用最大堆大小为200兆字节运行此问题。每次打开窗口,它会消耗50兆字节的内存。打开三个窗口(使用150兆字节的RAM)。然后关闭这三个窗口(调用dispose),这应该释放内存。然后尝试打开第四个窗口。将抛出OutOfMemoryError,第四个窗口不会打开。
我看到其他答案表明,内存将在必要时自动释放,以避免耗尽,但似乎并没有发生。
package com.prosc.swing; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.NumberFormat; public class WindowLeakTest { public static void main(String[] args) { EventQueue.invokeLater( new Runnable() { public void run() { JFrame launcherWindow = new JFrame( "Launcher window" ); JButton launcherButton = new JButton( "Open new JFrame" ); launcherButton.addActionListener( new ActionListener() { public void actionPerformed( ActionEvent e ) { JFrame subFrame = new JFrame( "Sub frame" ) { private byte[] bigMemoryChunk = new byte[ 50 * 1024 * 1024 ]; //50 megabytes of memory protected void finalize() throws Throwable { System.out.println("Finalizing window (Never called until after OutOfMemory is thrown)"); super.finalize(); } }; subFrame.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE ); subFrame.add( new JLabel( "Nothing to see here" ) ); subFrame.pack(); subFrame.setVisible( true ); System.out.println( "Memory usage after new window: " + getMemoryInfo() ); } } ); launcherWindow.add( launcherButton ); launcherWindow.pack(); launcherWindow.setVisible( true ); new Timer( 5000, new ActionListener() { public void actionPerformed( ActionEvent e ) { System.gc(); System.out.println( "Current memory usage after garbage collection: " + getMemoryInfo() ); } } ).start(); } } ); } public static String getMemoryInfo() { NumberFormat numberFormat = NumberFormat.getNumberInstance(); return "Max heap size is " + numberFormat.format( Runtime.getRuntime().maxMemory() ) + "; free memory is " + numberFormat.format( Runtime.getRuntime().freeMemory() ) + "; total memory is " + numberFormat.format( Runtime.getRuntime().totalMemory() ); } }
如此处所示,由于典型的主机对等组件而存在无法恢复的分配,存在一个不可约的泄漏。 在创建和处理 ~103 个窗口的过程中,剩余物大约为 2 MB。 在您的情况下,主要泄漏是由于保留了bigMemoryChunk
的实例。其中一种方法是在WindowListener
中使实例变得unreachable
。
this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { bigMemoryChunk = null; } });
为什么要将
bigMemoryChunk = null
设置为null?
JFrame
没有直接的方法可以知道您的程序中的每个实例都与一个关联的bigMemoryChunk
实例相关联。 当一个对象变得unreachable
时,它就会变为可回收的垃圾收集对象; 在这种情况下,bigMemoryChunk
是该数组对象的唯一引用,因此将其设置为null
将使其立即成为垃圾回收的候选对象。
如果
JFrame
是唯一持有对bigMemoryChunk
的引用的东西…那么为什么窗口被处理后,JFrame
和bigMemoryChunk
都不被垃圾回收呢?
您可能会混淆容器和继承和组合。 JFrame
没有“保持对bigMemoryChunk
的引用”;JFrame
具有一个名为bigMemoryChunk
的实例变量,该变量持有对数组对象的引用。由主机拥有并管理对等组件所消耗的少量内存。 bigMemoryChunk
中的大量内存是您的程序的责任。所附的WindowListener
允许您将数组对象的管理与关闭窗口关联起来。
下面的配置文件显示了一系列四个子帧的打开;每个子帧随后关闭,接着通过配置文件进行了强制垃圾收集。
如配置文件中所示:
import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.text.NumberFormat; public class WindowLeakTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { JFrame launcherWindow = new JFrame("Launcher window"); launcherWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); JButton launcherButton = new JButton("Open new JFrame"); launcherButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JFrame subFrame = new JFrame("Sub frame") { private byte[] bigMemoryChunk = new byte[50 * 1024 * 1024]; { this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { bigMemoryChunk = null; } }); } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("Finalizing window."); } }; subFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); subFrame.add(new JLabel("Nothing to see here")); subFrame.pack(); subFrame.setVisible(true); } }); launcherWindow.add(launcherButton); launcherWindow.pack(); launcherWindow.setVisible(true); } }); } }