JDK-8145984 : [macosx] sun.lwawt.macosx.CAccessible leaks
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.accessibility
  • Affected Version: 8u66,9
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: os_x
  • CPU: generic
  • Submitted: 2015-12-22
  • Updated: 2017-02-22
  • Resolved: 2016-05-04
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9 Other
8u112Fixed 9 b118Fixed openjdk7uFixed
Description
When a11y is in action and CAccessible objects get created they don't go away as expected and are kept held by a native code via JNIGlobalRef, at the same time leaking a peered Component (along with its whole hierarchy) via the CAccessible.accessible field.

It can be easily reproducible with the following test case and some memory profiler (like NB or YourKit).

1) Compile and run it with the profiler. A frame with a button appears.
2) Activate VoiceOver a11y tool (CMD+F5).
3) Press the button. A dialog with a text field appears.
4) Press ENTER, the dialog gets disposed.
5) Keep pressing the button and then ENTER for a number of times (~10).

Expected behavior:

All Dialog instances should be GC'ed (force GC in the profiler) when no dialog is shown.

Actual behavior:

Instances of the following classes are tracked in the memory dump: Dialog, JTextField, AccessibleJTextField, CAccessible. The latter is kept by a JNIGlobalRef. 

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

public class Main {

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            final JFrame frame = new JFrame("AX");
            JButton button = new JButton("open");
            button.addActionListener((e) -> {
                new MyDialog(frame).setVisible(true);
            });
            frame.add(button);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        });
    }
}

class MyDialog extends Dialog {

    private static int counter;

    public MyDialog(Frame owner) {
        super(owner, "Dialog");
        JTextField textField = new JTextField("" + ++counter);
        add(textField);
        textField.addActionListener((e) -> {
            MyDialog.this.dispose();
        });
        pack();
        setLocationRelativeTo(null);
    }
}
----------------------------

Comments
webrev: http://cr.openjdk.java.net/~ant/JDK-8145984/jdk9/webrev.2 review: http://mail.openjdk.java.net/pipermail/awt-dev/2016-April/011020.html
04-05-2016

webrev (the fix is identical on 8u/9): http://cr.openjdk.java.net/~ant/JDK-8145984/jdk8u/webrev.0 http://cr.openjdk.java.net/~ant/JDK-8145984/jdk9/webrev.0 1) JNI objects are deleted either explicitly or via Push/PopLocalFrame. However, not every call to a method producing a jobject is wrapped with Push/Pop. For instance, the following a11y methods are called directly by Cocoa and they don't delete JNI objects: - In JavaTextAccessibility: accessibilityValueAttribute, accessibilityIsValueAttributeSettable, accessibilityNumberOfCharactersAttribute. Somewhere, a JNIGlobalRef is created out of a JNILocalRef but the latter is not deleted. Provided that AWT runs an event loop, this all leak extensively when a11y is activated. The fix addresses these cases and actually does some additional cleanup, covering more cases not mentioned here (some cleanup may seem redundant but I did that to make sure there're no misses). 2) java CAccessible is peered to a native JavaComponentAccessibility (via sharing a native ptr). The latter is retained with CFRetain so that its removal is triggered by CAccessible, not natively. At the same time, CAccessible is referenced via JNIGlobalRef as JavaComponentAccessibility.fAccessible (see initWithParent, where JavaComponentAccessibility.fComponent is also turns to JNIGlobalRef). As I wrote in the previous comment, CAccessible relies on "finalize" in releasing native resources. In order to break this chain, in the fix, an AccessibleContext is disposed explicitly on removal of the component it's associated with. AC in its turn disposes "nativeAXResource" which is set to CAccessible (the field is used on OSX only).
22-12-2015

Thanks, nice to see you! ;)
22-12-2015

Welcome! =)
22-12-2015

There're two problems here: 1) Related native JNIGlobalRef's are not deleted when necessary. 2) CAccessible disposal scheme looks insufficient. CAccessible extends CFRetainedResource which defines "dispose" method to release native CF resources. The "dispose" method is called from "finalize" only. However, a CAccessible instance itself is kept by the native resources supposed to be released on its finalization which seems a self-contained chain.
22-12-2015