JDK-6354655 : JDialog memory not released when created from popup menu
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 1.3.0,1.3.1,1.4.0,1.4.2,5.0
  • Priority: P3
  • Status: Closed
  • Resolution: Not an Issue
  • OS:
    linux,solaris_7,solaris_8,windows_nt,windows_2003 linux,solaris_7,solaris_8,windows_nt,windows_2003
  • CPU: x86,sparc
  • Submitted: 2005-11-23
  • Updated: 2011-02-16
  • Resolved: 2005-11-30
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
J@SDK 1.4.2

ADDITIONAL OS VERSION INFORMATION :
Windows 2000

A DESCRIPTION OF THE PROBLEM :
I have an Action object whose doAction() function creates multiple JDialogs in succession (reusing the reference each time).  If this Action Object's doAction() function is called from a popup or pulldown menu, the memory for all the JDialogs is never released.  If this exact same call is made from the startup main, memory reclamation proceeds as expected.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
I've included three simple classes.  'KevAppWindow' (a Jframe) which instantiates the action ('KevDFAction')and sets up the popup menu.  The 'KevDFAction's doAction() creates many 'KevDialog's (which are just JDialogs which take up memory) while reusing a single reference to the dialogs.  Compile all three, and place in the same folder.  Run 'KevAppWindow's main function which will show a blank window, then right-click within the frame to get the popup.  Select the only choice "KevAction" and the output will report dialogs being created, and never finalized.  You will quickly run out of memory.
   To see the same action perform proper garbage collection, uncomment line 3 of the 'KevAppWindow's main function.  This will make the same call on startup without going thru the popup.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Starting application C:\EDNA\EdnaProject\kevin\KevAppWindow.class
Command line: "C:\j2sdk1.4.2_06\jre\bin\java.exe" -classpath C:\EDNA\EdnaProject kevin.KevAppWindow
The current directory is: C:\EDNA\EdnaProject\kevin
Kev Action
*******Instantiating KevDialog: count= 1
*******Instantiating KevDialog: count= 2
*******Finalizing KevDialog: count= 1
*******Instantiating KevDialog: count= 2
*******Instantiating KevDialog: count= 3
*******Instantiating KevDialog: count= 4
*******Finalizing KevDialog: count= 3
*******Instantiating KevDialog: count= 4
*******Instantiating KevDialog: count= 5
*******Finalizing KevDialog: count= 4
*******Instantiating KevDialog: count= 5
*******Instantiating KevDialog: count= 6
*******Finalizing KevDialog: count= 5
*******Instantiating KevDialog: count= 6
*******Instantiating KevDialog: count= 7
*******Finalizing KevDialog: count= 6
*******Instantiating KevDialog: count= 7
*******Instantiating KevDialog: count= 8
*******Finalizing KevDialog: count= 7
*******Finalizing KevDialog: count= 6
*******Finalizing KevDialog: count= 5
*******Finalizing KevDialog: count= 4
*******Finalizing KevDialog: count= 3
*******Finalizing KevDialog: count= 2
...
...
...contiunues to completion
ACTUAL -
Starting application C:\EDNA\EdnaProject\kevin\KevAppWindow.class
Command line: "C:\j2sdk1.4.2_06\jre\bin\java.exe" -classpath C:\EDNA\EdnaProject kevin.KevAppWindow
The current directory is: C:\EDNA\EdnaProject\kevin
Kev Action
*******Instantiating KevDialog: count= 1
*******Instantiating KevDialog: count= 2
*******Instantiating KevDialog: count= 3
*******Instantiating KevDialog: count= 4
*******Instantiating KevDialog: count= 5
*******Instantiating KevDialog: count= 6
*******Instantiating KevDialog: count= 7
*******Instantiating KevDialog: count= 8
*******Instantiating KevDialog: count= 9
...
...
...
*******Instantiating KevDialog: count= 625
*******Instantiating KevDialog: count= 626
*******Instantiating KevDialog: count= 627
*******Instantiating KevDialog: count= 628
*******Instantiating KevDialog: count= 629
*******Instantiating KevDialog: count= 630
*******Instantiating KevDialog: count= 631
*******Instantiating KevDialog: count= 632
*******Instantiating KevDialog: count= 633
*******Instantiating KevDialog: count= 634
*******Instantiating KevDialog: count= 635
*******Instantiating KevDialog: count= 636
*******Instantiating KevDialog: count= 637
*******Instantiating KevDialog: count= 638
java.lang.OutOfMemoryError


ERROR MESSAGES/STACK TRACES THAT OCCUR :
Starting application C:\EDNA\EdnaProject\kevin\KevAppWindow.class
Command line: "C:\j2sdk1.4.2_06\jre\bin\java.exe" -classpath C:\EDNA\EdnaProject kevin.KevAppWindow
The current directory is: C:\EDNA\EdnaProject\kevin
Kev Action
*******Instantiating KevDialog: count= 1
*******Instantiating KevDialog: count= 2
*******Instantiating KevDialog: count= 3
*******Instantiating KevDialog: count= 4
*******Instantiating KevDialog: count= 5
*******Instantiating KevDialog: count= 6
*******Instantiating KevDialog: count= 7
*******Instantiating KevDialog: count= 8
*******Instantiating KevDialog: count= 9
...
...
...
*******Instantiating KevDialog: count= 625
*******Instantiating KevDialog: count= 626
*******Instantiating KevDialog: count= 627
*******Instantiating KevDialog: count= 628
*******Instantiating KevDialog: count= 629
*******Instantiating KevDialog: count= 630
*******Instantiating KevDialog: count= 631
*******Instantiating KevDialog: count= 632
*******Instantiating KevDialog: count= 633
*******Instantiating KevDialog: count= 634
*******Instantiating KevDialog: count= 635
*******Instantiating KevDialog: count= 636
*******Instantiating KevDialog: count= 637
*******Instantiating KevDialog: count= 638
java.lang.OutOfMemoryError


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
FILE 1:
package kevin;

import java.awt.event.*;
import javax.swing.*;

/**
 * This class developed in stripped down version to show the JAVA memory leak.
 * If the call to kevDFAction.doAction() is made from "main()" (uncomment the third line in "main()")
 * we can see the KevDialog's being instantiated and finalized (reclaimed).  But, if this same call
 * to kevDFAction.doAction() is made from a popup (right-click within the frame), the memory is never
 * reclaimed, and in fact results in a java.lang.OutOfMemoryError very quickly.
 */

public class KevAppWindow extends JFrame implements MouseListener {
	
	public static 		KevDFAction		kevDFAction;
	private 			JPopupMenu 		popupMenu;
	
	public static final	boolean		ENABLED 	= true;
	public static final	boolean		DISABLED	= false;
	
	
	public KevAppWindow (String title) {
		super (title);
		addMouseListener (this);
		setSize(620, 220);
		
		kevDFAction			= new KevDFAction (ENABLED);
		
		popupMenu = new JPopupMenu();
		popupMenu.add (kevDFAction);	//in search of the memory leak
	}
	
	
	public void mousePressed(MouseEvent e) {		//kaw is a mouseListener
		doPopup(e);
	}
	public void mouseClicked(MouseEvent e) {
		doPopup(e);
	}
	
	public void mouseReleased(MouseEvent e) {
		doPopup(e);
	}
	
	public void mouseEntered(MouseEvent e) {}
	
	public void mouseExited(MouseEvent e) {}
	
	private void doPopup(MouseEvent e) {
		if (e.isPopupTrigger()) {
			popupMenu.show(this, e.getX(), e.getY());		//x & y of position of mouse during rt-click
		}
	}
	
	public static void main (String args[]) {
		KevAppWindow kaw = new KevAppWindow ("To show Java Memory Leak, right click for popup and Select!");
		kaw.setVisible (true);
		//kaw.kevDFAction.doAction();	//no memory leak if run from here, but call this from a popup and she runs out of memory right away
	}
}

FILE2:
package kevin;

import javax.swing.*;
import java.awt.event.*;


public class KevDFAction extends AbstractAction {
	
	public KevDFAction(boolean enabled ) {
		super ("Kev Action");
	}
	
	public void doAction () {
		System.out.println("Kev Action");
		for (int j = 0; j < 10000; j++) {
			KevDialog kDialog = new KevDialog();
			kDialog.dispose();	//don't need this, but just to make the point
		}
	}
	
	public void actionPerformed(ActionEvent e)
	{
		//System.out.println("Action Performed");
		doAction();
	}
	
	public static void main (String[] args) {		//in search of the memory leak
		KevDFAction kAction = new KevDFAction(true);
		kAction.doAction();
	}
}


FILE 3:
package kevin;

import javax.swing.*;
import java.lang.*;
import java.awt.*;


public class KevDialog extends JDialog {
	
	private String[] options = new String[1000];
	private static int dialogCount;
	
	public KevDialog() {
		++dialogCount;
		System.out.println("*******Instantiating KevDialog: count= " + dialogCount);
		for (int i = 0; i < 1000; i++) {
			options[i] = new String("OPTION_Take up some space_" + i);
		}
	}
	
	public void finalize() throws Throwable {
		--dialogCount;
		System.out.println("*******Finalizing KevDialog: count= " + dialogCount);
		super.finalize();
	}
	
	public static void main (String[] args) {
		for (int j = 0; j < 10000; j++) {
			KevDialog kDialog = new KevDialog();
			kDialog.dispose();
		}
	}
}



---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
I have to make all my large jobs standalone programs as I can't incorporate them into a menu system.  Very user unfriendly.

Comments
EVALUATION Bug marked as 'not a defect'. Let me explain the decision. It would be better to rewrite testcase to make sure that GC will work correctly: 1) System.runFinalization() / System.gc() should be added to force object finalization and garbage collection. Another important thing that leads to memory leak is 2) AWTEvent (COMPONENT_MOVED) will be posted to EDT when creating the window. It helds reference to the window (as source of the event) and it won't be dispatched until loop ending happen. So behaviour is correct. It's possible to workaround it: After the creation of the window to place this Toolkit.getSystemEventQueue.push(new UserEventQueue()); It helps to process events immediatelly by UserEventQueue and clear references to windows.
30-11-2005

EVALUATION This bug is reproducible using more simpler test. Looks like it's enough to place Window creation into listener processing to make the bug visible. E.g. button.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent ae){ for (int j = 0; j < 10000; j++) { Frame frame = new Frame(); frame.dispose(); } } } ); But interesting fact is that memory leak isn't reproducible if move this loop from the listener. I've tried to track object allocation using hprof. It happens that only WeakReference/Finilazer objects is kept alive (so Window as well). Another issue that on Solaris moving loop from the listener doesn't help and the bug still reproducible.
24-11-2005