Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-6471044

Memory leak in native cursor code

XMLWordPrintable

    • x86
    • windows_xp

      While investigating 6462383, I found a leak involving native cursor code in AWT. I've reproduced this on Windows XP 1.5.0_08 (b03) and later. 1.5.0_07 does not contain this bug. This bug existed in 6.0 as of b61 and before (including b51 and b45), but does not exist in b64 or later.

      The bug can be reproduced with the included test case:
      1) Click the "Create Big Frame" button
      2) Mouse over the Big Frame - the cursor will change to crosshairs (note that changing the cursor is not necessary for demonstrating the leak, it just makes it easier to recognize)
      3) Close the Big Frame using the close box
      4) Repeatedly clicking the GC button shows that Big Frame's memory is still being held onto.

      Note that if you close the Big Frame without moving the mouse into the client area, the leak does not happen.

      Here's my test:
      Leak3AWT.java
      -------------
      // Demonstrate leak w/ Cursor
      import java.awt.event.*;
      import java.awt.*;

      public class Leak3AWT extends Frame implements ActionListener {
          Button gcBtn;
          Button awayBtn;

          public Leak3AWT() {
              super("Leak3AWT");
              Panel btnPnl = new Panel();
              btnPnl.setLayout(new FlowLayout());

              gcBtn = new Button("GC");
              gcBtn.addActionListener(this);
              btnPnl.add(gcBtn);

              awayBtn = new Button("Create Big Frame");
              awayBtn.addActionListener(this);
              btnPnl.add(awayBtn);

              add(btnPnl, BorderLayout.SOUTH);
              addWindowListener(new WL());
              setFocusableWindowState(false);
          }

          public void actionPerformed(ActionEvent e) {
              Object src = e.getSource();
              if (src == gcBtn) {
                  System.gc();
              }
              else if (src == awayBtn) {
                  LeakFrame f = new LeakFrame(getBounds(), false);
                  f.setVisible(true);
              }
          }

          static class LeakFrame extends Frame {
              byte[] bigLeak;

              public LeakFrame(Rectangle parentBounds, boolean cover) {
                  super("Big Frame");
                  bigLeak = new byte[1024 * 1024 * 24];
                  if (cover) {
                      setBounds(parentBounds.x, parentBounds.y,
                                parentBounds.width + 10,
                                parentBounds.height + 10);
                  }
                  else {
                      setBounds(parentBounds.x,
                                parentBounds.y + parentBounds.height + 10,
                                parentBounds.width + 10,
                                parentBounds.height + 10);
                  }
                  add(new Label("Mouse over me to leak memory"));
                  addWindowListener(new WL());
                  setFocusableWindowState(false);
                  setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
                  setVisible(true);
              }

              public void dispose() {
                  System.out.println("dispose() called");
                  super.dispose();
              }
          }

          static class WL implements WindowListener {
                     public void windowClosed(WindowEvent e) {
              }
              public void windowActivated(WindowEvent e) {
              }
              public void windowClosing(WindowEvent e){
                  System.out.println("closing");
                  Window src = ((Window)e.getSource());
                  src.setVisible(false);
                  src.dispose();
              }
              public void windowDeactivated(WindowEvent e){}
              public void windowDeiconified(WindowEvent e){}
              public void windowIconified(WindowEvent e){}
              public void windowOpened(WindowEvent e) {}
          }
          
          public static void main(String[] args) {
              Leak3AWT f = new Leak3AWT();
              f.pack();
              f.setVisible(true);
          }
      }
      ---
      YourKit indicated the reference was coming from a JNI GlobalRef. Further debugging suggested possible problems with the following native functions, which create GlobalRefs that don't appear to get Deleted:

      awt_Component.cpp:
      jobject AwtComponent::FindHeavyweightUnderCursor(BOOL useCache) {
      ...

      found:
          jobject localRef = comp->GetTarget(env);
      --> jobject globalRef = env->NewGlobalRef(localRef);
          env->DeleteLocalRef(localRef);
          return globalRef;
      }

      awt_Cursor.cpp:
      JNIEXPORT void JNICALL
      Java_sun_awt_windows_WGlobalCursorManager_setCursor(JNIEnv *env, jobject,
                                                        jobject, jobject cursor, jboolean u)
      {
          TRY;

          if (cursor != NULL) { // fix for 4430302 - getCursor() returns NULL
             GlobalSetCursorStruct data;
      --> data.cursor = env->NewGlobalRef(cursor);
             data.u = u;
             AwtToolkit::GetInstance().InvokeFunction(
                    GlobalSetCursor,
                    (void *)&data);
          } else {
              JNU_ThrowNullPointerException(env, "NullPointerException");
          }
          CATCH_BAD_ALLOC;
      }

      Note that unlike 6469530 (Memory leak in the focus subsystem), which only seems to leak a single Frame, this bug leaks as many Frames as you create. My test gets an OOM on the 3rd Frame.

            Unassigned Unassigned
            bchristi Brent Christian
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: