United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-6462383 Unbounded memory leak in Windows XP JRE for Applets and applications that open JFrame's
JDK-6462383 : Unbounded memory leak in Windows XP JRE for Applets and applications that open JFrame's

Details
Type:
Bug
Submit Date:
2006-08-22
Status:
Closed
Updated Date:
2011-03-08
Project Name:
JDK
Resolved Date:
2011-03-08
Component:
client-libs
OS:
solaris_10
Sub-Component:
javax.swing
CPU:
x86
Priority:
P4
Resolution:
Fixed
Affected Versions:
5.0
Fixed Versions:

Related Reports
Backport:
Relates:
Relates:
Relates:
Relates:

Sub Tasks

Description
FULL PRODUCT VERSION :
1.5.0_08

ADDITIONAL OS VERSION INFORMATION :
Windows XP

A DESCRIPTION OF THE PROBLEM :
The test program does demonstrate a highly reproducible memory leak problem that is very serious (at least for the JVM on Windows XP ). 

The small test case allows you to create a frame and add tabbed components, each of which take 4MB of memory. It has buttons that force GC, and shows memory. Run the program and add tabs until you are close to the max memory for the JVM. 

Close the tabbed frame. It is set to dispose, and the reference to the tabbed frame is removed in a window listener. 

The "Update Memory Info" button reveals memory is not reclaimed. Hit "New Data" again, and try to add tabs to again reach the max memory. This time you get "OutOfMemoryError". Basically, the memory allocated by the tabbed frame never goes away until the application is stopped. This behavior occurs on Windows XP. 

On Solaris the behavior is as expected. One can add "New Data" until
"OutOfMemoryError", close the tabbed frame, and then do a whole new
series of "New Data", indefinitely.

When the test case is run as an applet, the same behavior occurs with
respect to the tabbed frame, but the memory leaking problem is even
worse. Now, even the memory allocated by the applet never gets
reclaimed. If the applet is navigated to, and the memory examined, and
then the back / front browser  buttons are used to destroy the applet
and create a new one, the memory reveals the first applets memory is
still allocated. If the tabbed frame is opened from the applet, and then
the brower back button is used to destroy the applet, the memory for the
applet and all of the memory allocated by the tabbed frame is never
freed (until the browser is killed). This is despite the fact that on
destroy the test applet asks the tabbed from to remove all components.

Again, this behavior is seen on Windows XP (SP2) with 1.5.0_08 but not
on Solaris 10 (same version of JDK).


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
As per description

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No memory leak in the Applets / applications that open JFrame's
ACTUAL -
Memory leak.

---------- BEGIN SOURCE ----------
Test case TestApplet.java:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.InvocationTargetException;

public class TestApplet extends JApplet implements WindowListener {

    private static int frameCounter;
    private JLabel memoryLabel;
    private TabbedFrame atf;
    private byte[] spaceHog = new byte[1024 * 1024 * 4];
    private int classFrameCounter;

    public void destroy() {
        if (atf != null) {
            atf.removeAll();
            atf.dispose();
            atf = null;
        }
        super.destroy();
    }

    public void init() {
        memoryLabel = new JLabel("Hit the update button");
        JButton updateMemory = new JButton("Update Memory Info");
        updateMemory.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                // Stimulate the gc system to get new memory -- this must force gc on old objects.
                int megs = 7;
                byte[][] bytes = new byte[2000][];
                int i = 0;
                while (true) {
                    try {
                        bytes[i % 2000] = new byte[megs * 1024 * 1024];
                        i++;
                    } catch (OutOfMemoryError e1) {
                        bytes = null;
                        break;
                    }
                }
                bytes = null;
                final Runtime rt = Runtime.getRuntime();
                rt.gc();
                rt.gc();
                final long maxMem = rt.maxMemory();
                final long totalMem = rt.totalMemory();
                final long freeMem = rt.freeMemory();
                final double bpmb = 1024 * 1024;
                memoryLabel.setText(String.format("Max=%1.2f MB Total=%1.2f MB Free=%1.2f MB Used=%1.2f MB", maxMem / bpmb,
totalMem / bpmb, freeMem / bpmb, (totalMem - freeMem) / bpmb));
            }
        });

        final JButton frameButton = new JButton("New Data");
        frameButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (atf == null) {
                    atf = new TabbedFrame((++frameCounter) + " : " +
(++classFrameCounter));
                   
atf.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                    atf.addWindowListener(TestApplet.this);
                    atf.validate();
                    atf.setVisible(true);
                } else {
                    atf.addData();
                    atf.setVisible(true);
                }
            }
        });

        final JButton clearButton = new JButton("Clear All Tabs");
        clearButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (atf != null) {
                    atf.clearData();
                    atf.setVisible(true);
                }
            }
        });

        final JPanel buttonPanel = new JPanel();
        buttonPanel.add(updateMemory);
        buttonPanel.add(frameButton);
        buttonPanel.add(clearButton);

        add(memoryLabel, BorderLayout.NORTH);
        add(buttonPanel, BorderLayout.SOUTH);
    }

    public void windowActivated(final WindowEvent e) {
    }

    public void windowClosed(final WindowEvent e) {
        final Object src = e.getSource();
        if (src == atf) {
            System.out.println("Frame closed. Reference to frame nulled.");
            atf = null;
        }
    }

    public void windowClosing(final WindowEvent e) { }
    public void windowDeactivated(final WindowEvent e) { }
    public void windowDeiconified(final WindowEvent e) { }
    public void windowIconified(final WindowEvent e) { }
    public void windowOpened(final WindowEvent e) { }

    public static class TabbedFrame extends JFrame {
        private int counter = 0;
        private JTabbedPane tabbedPane;
        private byte[] bigData = new byte[1024 * 1024 * 4];
        public TabbedFrame(final String title) throws HeadlessException{
            setTitle("Test Frame " + title);
            setSize(500, 400);
            tabbedPane = new JTabbedPane();
            add(tabbedPane);
        }

        public void addData() {
            tabbedPane.addTab(Integer.toString(counter++), new
DataPanel());
        }

        public void clearData() {
            final Component[] c = tabbedPane.getComponents();
            for (int i = 0; i < c.length; i++) {
                tabbedPane.remove(c[i]);
            }
        }

        private static class DataPanel extends JPanel {

            private byte[] bigData = new byte[1024 * 1024 * 4];
            public DataPanel() {
                add(new JButton("Proxy for data showing panel"));
            }
        }
    }

    public static void main(final String[] args) throws
InvocationTargetException, InterruptedException {
        final TestApplet applet = new TestApplet();
        EventQueue.invokeAndWait(new Runnable() {
            public void run() {
                final JFrame frame = new JFrame("Myriad Test");

                applet.init();
                applet.start();

                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(applet, BorderLayout.CENTER);
                frame.setSize(500, 200);
                frame.validate();
                frame.setVisible(true);
            }
        });
    }
}
---------- END SOURCE ----------

REPRODUCIBILITY :
This bug can be reproduced always.

                                    

Comments
EVALUATION

Reproducible as described with 1.5.0_06 & _08, 1.6b96, and (with minor modification) 1.4.2_12.
                                     
2006-08-24
WORK AROUND

When running the test case with Java 6.0 (at least as an application - I haven't tried w/ applets), the leak disappears once the main window has had a native expose event handled by the RepaintManager.  An easy way to make this happen is to minimze and then restore the main window.
                                     
2006-08-30
EVALUATION

My profiler shows that in 6.0 the TabbedFrame is being held onto by the RepaintManager.tmpHWDirtyComponents map.  This map was added for the "gray rect" fix (4967886), which would mean that this is only the root cause for 6.0.  The tmpHWDirtyComponents is cleared when a native expose happens, so I was able to work around this bug my minimizing then restoring the main frame.  As expected, this workaround did not work with 5.0_08.  I was unable to workaround the bug disabling the gray rect fix.

I'll next investigate the root cause for 5.0.
                                     
2006-08-30
EVALUATION

I believe I've tracked down the pre-6.0 cause for this bug, and have filed a bug:
6469530 : Memory leak in the focus subsystem
                                     
2006-09-12
EVALUATION

I believe I've found another leak besides the one in 6469530.  Stay tuned while I track it down...
                                     
2006-09-13
EVALUATION

It is so.  5.0u8 is subject to 6471044 : Memory leak in native cursor code.
                                     
2006-09-14
WORK AROUND

Unfortunately, there is no workaround for 1.5.0_08 due to 6471044.  However, if one were *desperate* to workaround this bug with a 1.5 build, one could use 1.5.0_07, which is not subject to 6471044.  One would still need to perform the workaround for 6469530, namely giving focus to some native app, then returning to the Java app.  This works for me with TestApplet.java.
                                     
2006-09-14
EVALUATION

6471044 (marked as a duplicate of 6351698) is fixed in 1.5.0_09b02.  I have confirmed that this eliminates this bug in 5.0 releases for both applications and applets.
                                     
2006-10-04
SUGGESTED FIX

RepaintManager.java-        Tue Oct 10 15:06:51 2006
--- RepaintManager.java        Tue Oct 10 14:42:02 2006

*** 67,77 ****
      // is scheduled.  When the work request is processed all entries in
      // this map are pushed to the real map (dirtyComponents) and then
      // painted with the rest of the components.
      //
      private Map<Container,Rectangle> hwDirtyComponents;
-     private Map<Container,Rectangle> tmpHWDirtyComponents;
  
      private Map<Component,Rectangle> dirtyComponents;
      private Map<Component,Rectangle> tmpDirtyComponents;
      private java.util.List<Component> invalidComponents;
  
--- 67,76 ----

*** 264,274 ****
          synchronized(this) {
              dirtyComponents = new IdentityHashMap<Component,Rectangle>();
              tmpDirtyComponents = new IdentityHashMap<Component,Rectangle>();
              this.bufferStrategyType = bufferStrategyType;
              hwDirtyComponents = new IdentityHashMap<Container,Rectangle>();
-             tmpHWDirtyComponents = new IdentityHashMap<Container,Rectangle>();
          }
      }
  
      private void displayChanged() {
          clearImages();
--- 263,272 ----

*** 492,504 ****
          synchronized(this) {
              if (hwDirtyComponents.size() == 0) {
                  return;
              }
              hws = hwDirtyComponents;
!             hwDirtyComponents = tmpHWDirtyComponents;
!             tmpHWDirtyComponents = hws;
!             hwDirtyComponents.clear();
          }
          for (Container hw : hws.keySet()) {
              Rectangle dirty = hws.get(hw);
              if (hw instanceof Window) {
                  addDirtyRegion((Window)hw, dirty.x, dirty.y,
--- 490,500 ----
          synchronized(this) {
              if (hwDirtyComponents.size() == 0) {
                  return;
              }
              hws = hwDirtyComponents;
!             hwDirtyComponents =  new IdentityHashMap<Container,Rectangle>();
          }
          for (Container hw : hws.keySet()) {
              Rectangle dirty = hws.get(hw);
              if (hw instanceof Window) {
                  addDirtyRegion((Window)hw, dirty.x, dirty.y,
                                     
2006-10-04



Hardware and Software, Engineered to Work Together