JDK-6471044 : Memory leak in native cursor code
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 5.0u8
  • Priority: P2
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2006-09-14
  • Updated: 2014-02-27
  • Resolved: 2006-09-18
Related Reports
Duplicate :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
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.

Comments
EVALUATION This issue is very similiar to 6470522: in 6.0 this bug was fixed with the fix for 6351698 and therefore is not reproducible. In 5.0u8 this bug is introduced by partial backport of 5097531+6181157+6351698 - this is described in 6470522, so I'm closing this new bug as duplicate.
18-09-2006