Java Solaris Communities Sun Store Join SDN My Profile Why Join?
 
Bug Database
Bug Detail
Quick Lists
Top 25 Bugs
Top 25 RFE's
Recently Closed Bugs
Printable Page Printable Page


Bug Database
Bug ID: 6209673
Votes 19
Synopsis Memory leak in Swing applications after a display mode change
Category java:classes_swing
Reported Against
Release Fixed mustang(b94)
State 10-Fix Delivered, bug
Priority: 2-High
Related Bugs 4899321 , 6454418
Submit Date 16-DEC-2004
Description
FULL PRODUCT VERSION :
java version "1.5.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-b64)
Java HotSpot(TM) Client VM (build 1.5.0-b64, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Windows XP SP2
 customer  Windows XP [Version 5.1.2600]

EXTRA RELEVANT SYSTEM CONFIGURATION :
nVidia Geforce4 Ti 4200 graphics card.  nVidia drivers version 6.14.10.6177

A DESCRIPTION OF THE PROBLEM :
I have noticed that very basic drawing in a Swing application will eventually lead to a memory leak in the AWT Event thread that prevents re-painting.  All that is needed to demonstrate this is a Swing app that repaints the UI periodically.  I used a JFrame with a single JLabel and called repaint() ten times a second.
The tricky bit about the bug, is that it is triggered by a completely different process, in my case a fancy screen blanker that I believe uses OpenGL.   If I run the described Swing app and activate the GoldFish Aquarium screen saver from www.lifeglobe.com the swing application will lose a chunk of memory.  Simply repeatedly activating and deactivating the screen saver is enough to crash the Java application after only a few iterations.
The bug can be seen in Java 1.4.2_05 and Java 5.
I have only been able to reproduce this bug with that specific screen saver, only when the screen saver is set to 16 bpp and my desktop is set to 32 bpp.  Presumably the 3D screen savers included with windows (e.g. 3D Pipes) don't change the bit depth, or use Direct3D instead of openGL or vice versa, They don't appear to trigger the problem.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Install the Goldfish Aquarium screen saver from www.lifeglobe.com
Set your desktop resolution to 1920x1440 32bpp
Make sure the screen saver is configured for 16bpp
Compile and run the attached Java program from a console with no options (java GraphicLeak).
Repeatedly activate and de-activate the screen saver while watching memory usage of the java process with the Windows task manager.
Eventually you will see OutOfMemoryExceptions reported in the console window where you started the Java application.
For me it happens after activating the screen saver for the seventh time.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The java application should not lose memory when another process makes use of the graphics card.
ACTUAL -
Each invocation of the screen saver causes a memory loss in the java application.  The memory loss may be proportional to the size of the java application's UI.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap space

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
//
import java.awt.Container;
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class GraphicLeak
{
    public static void main(String[] args)
    {
        JFrame frm = new JFrame("Memory leak");
        Container cp = frm.getContentPane();
	cp.add( new JLabel(
            "<html>Launch Windows Task Manager<br>Maximize the size fo this window.<br>"+
            "Watch the memory consumption of this process.<br>"+
            "See that it is in a steady state.<br>"+
            "Activate the \"Goldfish Aquarium\" screensaver<br>"+
            "(free trial www.lifeglobe.com 16bpp, default res. of 1920 x1440, limit framerate)<br>"+
            "Wait a few seconds minutes, stop the screen saver and look at the memory usage of this process.<br>"+
            "Repeat activating the screen saver and deactivating it, several times.<br>"+
            "Memory usage of this process appears to jump every time the screen saver starts and stops.<br>"+
            "If left long enough Out-of-memory errors will occur with every reapint.<br>"+
            "</html>"), BorderLayout.CENTER );

        frm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frm.pack();
        frm.setVisible(true);

        while(true)
        {
            frm.repaint();
            try { Thread.sleep(100); } catch (InterruptedException e ) {}
        }
    }
}
---------- END SOURCE ----------
  xxxxx@xxxxx   2004-12-16 19:36:05 GMT

This bug is a manifestation of an even simpler situation where _any_ display mode that occurs outside of the Java application (including any screen saver activating, or hibernating the system, or another app changing the display mode, or the user changing the display mode via the control panel) will cause the java application to leak memory.  If you want to reproduce the problem, run the app above (or any Swing application, actually) and change the color depth of the screen using the Display Control Panel.  Bring up the Taskmanager and track the memory usage of the Java app; you will see the memory size grow with every display change.  If you want to see dramatic leaks, increase the size of the Java window to be the same as your desktop size (maximize the window).

  xxxxx@xxxxx   2005-1-06 22:33:40 GMT
Work Around
Every time you change display do:

RepaintManager.setCurrentManager(null);

This will trigger creating a new RepaintManager which will flush the necessary caches.
  xxxxx@xxxxx   2005-2-17 22:51:49 GMT
Evaluation
The core problem here comes from the fix to an earlier bug related to multimon Swing apps: 
	4895978: High CPU utilization with Matrox graphic adapters and Multiple Display

The problem in that older bug was that Swing would recreate the back buffer every time it rendered to a new GraphicsConfiguration.  On a multimon system where Swing windows were on more than one display, this could happen as often as every frame; rendering first to one display and then to another would cause Swing to ditch and recreate the VolatileImage back buffer every time.

The fix to that bug was to cache the VolatileImage back buffer per-GC, so rendering to a different GraphicsConfig than last time would merely cause Swing to retrieve the appropriate volatileImage instead of creating a new one.

The problem with this bug (and with the old fix) was that _all_ volatileImage back buffers are now cached, whether their GraphicsConfigs are current/valid or whether they are obsolete and no longer usable.  In the case of a multimon system, there are multiple usable GraphicsConfigurations, so caching the VolatileImages is appropriate.  But in the case of a display mode switch, the old GraphicsConfiguration objects under the previous display mode get invalidated and will not be used again.  However, the fact that Swing has stashed a reference to the VolatileImage for this obsolete GraphicsConfiguration means that that VolatileImage will not get garbage collected, and thus any resources it allocated (such as an image to hold the pixel data) will continue taking up memory for the life of the process.

The best fix here may involve some new API for GraphicsConfiguration; we could add the capability for a GraphicsConfig to realize that it is invalid (it currently does not track this information, but it easily could) and add API to GraphicsConfiguration to query that flag:
	public boolean GraphicsConfiguration.isValid();

When Swing tries to get the VolatileImage for the current GraphicsConfiguration, if it gets "null" from the hashmap that stores
these images, that can be a signal that it is now in a new display mode
and that it should walk the hasmap and remove any obsolete entires.
For every volatileImage/GC pair in the map, it can query whether that
GC is valid and, if not, it should remove that entry from the map and thus make it available for garbage collecting.

  xxxxx@xxxxx   2005-1-06 22:33:40 GMT
One way to address this is for Swing to trash its volatile images cache
when a display mode change occurs. Fortunately we get a display mode
when the system wakes up after hibernation or sleep, so this should solve
the problem.
Posted Date : 2006-04-27 21:43:25.0

Since the suggested fix is in Swing code, reassigning to Swing.
Posted Date : 2006-05-08 17:14:34.0

Dmitri's suggestion is the way to go. The RepaintManager will add a DisplayChangeListener, when the display changes it'll nuke the cache.
Posted Date : 2006-06-07 23:17:07.0

4895978 was closed as a dup of 4899321. Refer to 4899321 for specifics.
Posted Date : 2006-07-17 15:52:20.0
Comments
  
  Include a link with my name & email   

Submitted On 20-JAN-2005
rcengels
Is there any sort of workaorund we can add to our applications in the mean time?


Submitted On 28-JAN-2005
jimnick
If you can predict the point at which any  mode switching is likely to happen, the following code seems to prevent the memory leaks:

---------- BEGIN SOURCE ----------
RepaintManager rm = RepaintManager.currentManager(null);
            Dimension oldSize = rm.getDoubleBufferMaximumSize();
            rm.setDoubleBufferMaximumSize(new Dimension(0, 0));
            rm.setDoubleBufferMaximumSize(oldSize);
---------- END SOURCE ----------

N.B. This should be executed in the Swing thread; also, i


Submitted On 13-SEP-2005
kass
The existing workarounds didn't work very well for me.  One hack I found was to subclass RepaintManager, copy and paste getVolatileOffscreenBuffer into the subclass, then add after "if (image != null) { image.flush(); } the following:

      if (volatileMap_.keySet().size() > 3)
      {
        for (VolatileImage vImage : volatileMap_.values())
        {
          vImage.flush();
        }
        volatileMap_.clear();
      }

and register the subclass as the official RepaintManager.  This will contain the leak, although since it only bounds the leak it can still lead to extra memory usage.


Submitted On 27-APR-2006
cowwoc
This issue is discussed in further detail here: http://weblogs.java.net/blog/enicholas/archive/2006/04/leaking_evil.html


Submitted On 26-DEC-2006
kass
I'm confused... this was marked a duplicate of a bug that was fixed in JDK 1.4.2... but this bug is definitely present in 1.5.0_xx releases.


Submitted On 05-FEB-2007
bruker
What does it mean "closed, fixed" ? I can reproduce this bug ( at least the behavior is the same ) in the current jdk 1.5.10 : after several screen resolution changes we get "OutOfMemoryError" in out application


Submitted On 18-JUL-2007
texclayton
I found that the problem persists on Windows XP with Java 1.6.0_02 when the nopixfmt flag is set to true.  I came up with the following workaround based on the comments in this bug:

//This class may fail to load if sun.awt.DisplayChangedListener does not exist.
private static class DisplayChangeHandler
	implements sun.awt.DisplayChangedListener, Runnable
{
	// We must keep a strong reference to the DisplayChangedListener,
	//  since SunDisplayChanger keeps only a WeakReference to it.
	private static	DisplayChangeHandler	displayChangeHack;
	static {
		try {
			GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
			GraphicsDevice[] devices = env.getScreenDevices();
			if ( displayChangeHack == null ) {
				displayChangeHack = new DisplayChangeHandler();
			}
			//Add ourselves as a listener to the GraphicsEnvironment if possible.
			env.getClass()
				.getMethod( "addDisplayChangedListener", new Class[] { sun.awt.DisplayChangedListener.class } )
				.invoke( env, new Object[] { displayChangeHack } );
		} catch (Throwable t) {
			System.out.println( "Could not create DisplayChangeHandler to plug mem leaks due to display changes: " + t );
		}
	}
	
	public void displayChanged()
	{
		EventQueue.invokeLater( this );
	}
	
	public void paletteChanged()
	{
		EventQueue.invokeLater( this );
	}
	
	public void run()
	{
		//Force the RepaintManager to clear out all of the VolatileImage back-buffers that it has cached.
		//	See Sun bug 6209673.
		RepaintManager rm = RepaintManager.currentManager(null);
		Dimension size = rm.getDoubleBufferMaximumSize();
		rm.setDoubleBufferMaximumSize(new Dimension(0, 0));
		rm.setDoubleBufferMaximumSize(size);
	}
}


Submitted On 08-SEP-2009
java_osborn
I cannot believe that this has not been fixed.  They say it is closed but I am seeing it in java 1.5.  Come on guys this is an easy fix!!!



PLEASE NOTE: JDK6 is formerly known as Project Mustang