JDK-4174998 : System.gc method doesn't work correctly on Solaris environment.
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 1.1.6
  • Priority: P1
  • Status: Closed
  • Resolution: Duplicate
  • OS: solaris_2.5.1
  • CPU: sparc
  • Submitted: 1998-09-21
  • Updated: 2022-10-21
  • Resolved: 1998-09-23
Related Reports
Duplicate :  
Relates :  
Description
This is a bug I filed on behalf of NTT Comware.

(1) Working environments
OS             : Solaris2.5.1 Japanese version
JRE            : JDK1.1.6N (green_threads)
Window         : CDE

(2) Phenomenon
Two sources named Frame1.java, StandardMenu.java are attached for reproducing
this phenomenon. The following show the steps to verify this phenomenon.

Step1: Compile sources: javac Frame1.java; javac StandardMenu.java.
Step2: Run Frame1.class as following.
       $jre -cp . Frame1
       Then a frame titled AWTapp appears.
Step3: Click the [open Child Frame] button on the AWTapp frame.
       Then a child frame appears.
Step4: Click the [close Child Frame] button on the parent frame.
       Then the child frame disappears.
Step5: Do Step3-Step4 several times.
Step6: Click the [call System.gc] button on the parent frame.
       You can see a message "System.gc called" showed on the screen
       which the command jre is executed on. 
       Here is the point. If you do this on Windows95, you will see
       other two pieces of message 
       "*******Frame1 finalize called*******"
       "*******StandardMenu finalize called*******".
       But on solaris, you cannot.

Step7: Click the [open Child Frame] on the parent frame
       to display the child frame.
Step8: Click the [remove Menu ActionListener] on the child frame.
Step9: Click the [close Child Frame] button on the parent frame 
       to close child frame. 
Step10:Repeat Step7-Step9 several times.
Step11:You can only see a message "Frame1 finalized called".
       But on Windows95, you can see two pieces of message
       "*******Frame1 finalize called*******"
       "*******StandardMenu finalize called*******".
       Although Frame1 is freed, but the StandardMenu is not be freed on Solaris.
       
Step12:Whatever you firstly click the [remove menubar] button and then close
       the child frame, the phenomena of Step6 and Step11 are the same.

/** -----Frame1.java------ */

import java.awt.*;
import java.awt.event.*;

public class Frame1 extends Frame implements ActionListener{
    StandardMenu    myMenuBar;
    Button closeChildFrameButton = new Button();
    GridLayout gridLayout1 = new GridLayout(5,1);
    Button openChildFrameButton = new Button();
    Button removeMenuAvtionListenerButton = new Button();

    Frame1  child;
    Button gcButton = new Button();
    Button removeMenubarButton = new Button();

    public Frame1() {
        this.setSize(400,400);
        myMenuBar = new StandardMenu();
        this.setMenuBar(myMenuBar);
        myMenuBar.exitMenuItem.addActionListener(this);
        try  {
            jbInit();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    //main
    public static void main(String[] args) {
        Frame1 frame11 = new Frame1();
        frame11.setVisible(true);
    }
    //exit menuItem action
    public void actionPerformed(ActionEvent e){
        this.dispose();
        System.exit(0);
    }

    private void jbInit() throws Exception {
        this.setLayout(gridLayout1);
        closeChildFrameButton.setLabel("close Child Frame");
        closeChildFrameButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(ActionEvent e) {
                closeChildFrameButton_actionPerformed(e);
            }
        });
        openChildFrameButton.setLabel("open Child Frame");
        openChildFrameButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(ActionEvent e) {
                openChildFrameButton_actionPerformed(e);
            }
        });

        removeMenuAvtionListenerButton.setLabel("remove Menu ActionListener");
        removeMenuAvtionListenerButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(ActionEvent e) {
                removeMenuAvtionListenerButton_actionPerformed(e);
            }
        });

        gcButton.setLabel("call System.gc()");
        gcButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(ActionEvent e) {
                gcButton_actionPerformed(e);
            }
        });


        removeMenubarButton.setLabel("remove menubar");
        removeMenubarButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(ActionEvent e) {
                removeMenubarButton_actionPerformed(e);
            }
        });

        this.add(openChildFrameButton, null);
        this.add(closeChildFrameButton, null);
        closeChildFrameButton.setEnabled(false);
        this.add(removeMenuAvtionListenerButton, null);
        this.add(gcButton, null);
        this.add(removeMenubarButton, null);
    }

    void openChildFrameButton_actionPerformed(ActionEvent e) {
        System.out.println("open child Frame called");
        child = new Frame1();
        child.setVisible(true);
        closeChildFrameButton.setEnabled(true);
        openChildFrameButton.setEnabled(false);
    }

    void closeChildFrameButton_actionPerformed(ActionEvent e) {
        System.out.println("close child Frame called");
        child.dispose();
        child = null;
        closeChildFrameButton.setEnabled(false);
        openChildFrameButton.setEnabled(true);
    }

    void removeMenuAvtionListenerButton_actionPerformed(ActionEvent e) {
        System.out.println("removeActionListener called");
        if(myMenuBar != null){
            myMenuBar.exitMenuItem.removeActionListener(this);
        }
        removeMenuAvtionListenerButton.setEnabled(false);
    }

    void gcButton_actionPerformed(ActionEvent e) {
        System.out.println("System.gc() called");
        System.gc();
    }

    void removeMenubarButton_actionPerformed(ActionEvent e) {
        System.out.println("remove menubar called");
        if(myMenuBar != null){
            remove(myMenuBar);
        }
        removeMenubarButton.setEnabled(false);
        myMenuBar = null;
    }
    public void finalize(){
        System.out.println("*********Frame1 finalize called**********");
    }

}

 /* -------StandardMenu.java------ */
import java.awt.*;
import java.awt.event.*;

public class StandardMenu extends MenuBar {

  Menu FileMenu = new Menu();
  MenuItem exitMenuItem = new MenuItem();


  public StandardMenu() {
    try {
      jbInit();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  private void jbInit() throws Exception {
    FileMenu.setLabel("File");
    this.add(FileMenu);
    exitMenuItem.setLabel("exit");

    FileMenu.addSeparator();
    FileMenu.add(exitMenuItem);
  }
  public void finalize(){
    System.out.println("*********StandardMenu finalize called*********");
  }
}

Comments
SUGGESTED FIX NTT Comware, a very important customer for us, has run into the memory leak problem and they need to fix them very soon. The memory leakage occurs because a static variable peerMap in sun.awt.SunToolkit class, which holds <Component>.<Peer> maps, references a peer associated with MenuBar, MenuItem, etc. When a Frame with a MenuBar is disposed, all peers associated with the frame should be removed from the static variable peerMap. But the original code forgets that. Therefore, on Solrais, when User creates and dispose a Frame over and over, peerMap grows putting garbage to itself, which is never reclaimed. On the other hand, JDK for Windows does not make use of peerMap, so there is no problem. Reading the original code carefully, you can find that targetDisposedPeer() method is the only method and never called for MenuBar, MenuItem, etc. Or you can make sure that, putting a debug code into sun.awt.SunToolkit to inspect the Hashtable peerMap. Because our customer need to fix the memory leak problem very soon, my suggestion intended to include a minimal change to jdk1.1.6, The attached diffs are the fixes for JDK 1.1.6. ------------------------------------------------------------------ tomatsu@chamonix[322] diff -c MMenuPeer.java ~/scholar/src/solaris/sun/sun/awt/motif/MMenuPeer.java *** MMenuPeer.java Thu Sep 24 14:17:29 1998 --- /home/tse/tomatsu/scholar/src/solaris/sun/sun/awt/motif/MMenuPeer.java Thu Apr 16 16:52:50 1998 *************** *** 62,69 **** public void delItem(int index) { } public void dispose() { - MToolkit.targetDisposedPeer(target, this); - if (!nativeCreated) { return; } --- 62,67 ---- tomatsu@chamonix[323] diff -c MMenuItemPeer.java ~/scholar/src/solaris/sun/sun/awt/motif/MMenuItemPeer.java *** MMenuItemPeer.java Thu Sep 24 14:17:03 1998 --- /home/tse/tomatsu/scholar/src/solaris/sun/sun/awt/motif/MMenuItemPeer.java Thu Apr 16 16:52:50 1998 *************** *** 90,97 **** native void pDisable(); public void dispose() { - MToolkit.targetDisposedPeer(target, this); - if (!nativeCreated) { return; } --- 90,95 ---- tomatsu@chamonix[324] diff -c MFramePeer.java ~/scholar/src/solaris/sun/sun/awt/motif/MFramePeer.java *** MFramePeer.java Thu Sep 24 14:52:14 1998 --- /home/tse/tomatsu/scholar/src/solaris/sun/sun/awt/motif/MFramePeer.java Thu Apr 16 16:52:50 1998 *************** *** 97,108 **** } public void dispose() { - MenuBar mb = ((Frame)target).getMenuBar(); - MMenuBarPeer mbpeer = (MMenuBarPeer) MToolkit.targetToPeer(mb); - if (mbpeer != null){ - MToolkit.targetDisposedPeer(mb, mbpeer); - mbpeer.dispose(); - } allFrames.removeElement(this); super.dispose(); } --- 97,102 ---- *************** *** 114,123 **** } public void setMenuBar(MenuBar mb) { - MenuBar old = null; - if (target != null){ - old = ((Frame)target).getMenuBar(); - } MMenuBarPeer mbpeer = (MMenuBarPeer) MToolkit.targetToPeer(mb); pSetMenuBar(mbpeer); if (target.isVisible()) { --- 108,113 ---- *************** *** 127,141 **** target.invalidate(); target.validate(); } - if (old != null){ - MMenuBarPeer peer = (MMenuBarPeer) MToolkit.targetToPeer(old); - if (peer != null){ - if (mb == null){ - MToolkit.targetDisposedPeer(old, peer); - peer.dispose(); - } - } - } } native void pSetMenuBar(MMenuBarPeer mbpeer); --- 117,122 ---- tomatsu@chamonix[340] diff -c MPopupMenuPeer.java ~/scholar/src/solaris/sun/sun/awt/motif/MPopupMenuPeer.java *** MPopupMenuPeer.java Thu Sep 24 15:18:58 1998 --- /home/tse/tomatsu/scholar/src/solaris/sun/sun/awt/motif/MPopupMenuPeer.java Thu Apr 16 16:52:54 1998 *************** *** 111,118 **** } public void dispose() { - MToolkit.targetDisposedPeer(target, this); - if (!nativeCreated) { return; } --- 111,116 ---
11-06-2004

EVALUATION The contract for System.gc and finalization is not sufficiently tight to guarantee the behavior that the user expects, but, from what I know of the implementation, this looks suspicious. william.maddox@Eng 1998-09-21 I think the comments are correct and that this is in someway an AWT problem. I'm refiling it. tom.rodriguez@Eng 1998-09-22 This is a duplicate of bug 4085578. This particular bug was mosly fixed in 1.2beta4. So i am closing this bug as a duplicate.
22-09-1998