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

Frame with MenuBar are not garbage collected

    XMLWordPrintable

Details

    • b03
    • generic, sparc
    • solaris_2.5.1, windows_nt

    Backports

      Description

        Name: joT67522 Date: 10/10/97

        Where to begin? A suggestion to the reader: pour yourself a nice big
        cup of coffee.

        Here are brief descriptions of three GC bugs.

        1) A Frame which has had the pack method called is not immediately
        garbage collected once disposed. It is only garbage collected once
        another Frame is created and has had pack called.

        2) A Frame which has had its menu bar set is not immediately garbage
        collected once disposed. Again, it is only garbage collected once
        another Frame is created and has had its menu bar set.

        3) A Frame which is an ActionListener for a MenuItem is NEVER garbage
        collected even after being disposed. This holds true even if the
        Frame is not itself an ActionListener but contains a Component which
        is an ActionListener for a MenuItem. This also holds true if the
        MenuItem is, or is part of, a PopupMenu.


        These bugs are extremely debilitating. The first two cause an
        unnecessary delay of the freeing of memory, but they pale in
        comparison to the third. Unless the programmer removes every
        ActionListener of every MenuItem, the third bug will cause a core leak
        roughly equivalent to the garbage collector never running -- that's an
        overstatement but a small one, in most GUI programs there are many,
        many objects reachable from a Frame.

        Components are given a special status when mapped such that they are
        not garbage collected while on the screen even if the program can no
        longer reach them. Good.

        When a Frame is disposed of each Component is disposed of and the
        special status is revoked allowing the Component to be garbage
        collected. Good.

        When a Frame is disposed of the Menus referred to by the Frame and its
        Components do NOT have their special status revoked. Bad.

        As a result, they are not allowed to be garbage collected. Therefore,
        their ActionListeners are not allowed to be garbage collected. In
        turn, the entire Component heirarchy for the Frame is not allowed to
        be garbage collected.

        ANY OBJECT REACHABLE FROM THE TOPLEVEL FRAME WILL NOT BE GARBAGE
        COLLECTED -- THIS COULD BE THE ENTIRE PROGRAM.

        The examples below demonstrate the first bug, the third bug, and a
        less than ideal workaround for the third bug. The workaround does not
        deal with PopupMenus which again prevent the entire Component
        heirarchy from being GCed. Only one example should be run at a time
        due to potential interactions. An example is run by uncommenting the
        line to call it in main, compiling, and executing.

        The examples will take some time to work through and understand, but
        they should demonstrate the bugs and the associated workaround.


        /**
         * <p>GCExample.java - Example Class showing the problems with Java GC</p>
         *
         * <p>Copyright (C) 1997 Carnegie Mellon University</p>
         *
         * <p> --- ATTENTION ---</p>
         * <p>Carefully read the terms and conditions of the COPYRIGHT
         * agreement included with this distribution before downloading,
         * installing, or using this software. By downloading, installing,
         * or using this software you indicate your acceptance of the terms and
         * conditions of the COPYRIGHT agreement. If you do not agree with any
         * term or condition, do not download, install, or use this software.</p>
         *
         *
         * @author Douglas Cunningham
         **/


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

        public class GCExample {

          /*
             This example demonstrates the fact that the last packed frame is
             not GCed until a new packed frame is created.
           */
          public static void packExample() {
            Frame1 f = null;

            System.out.println("Running packExample.");

            // This Frame will be GCed: no problem GCing unpacked Frames.
            f = new Frame1("Unpacked Frame 1");
            f.show();
            f.dispose();
            f = null;

            // This Frame will be GCed after the next packed Frame is created
            f = new Frame1("Packed Frame 1");
            f.pack(); // ONLY DIFFERENCE IS PACK!!!
            f.show();
            f.dispose();
            f = null;

            // This Frame will not be GCed because it is the last packed Frame
            // created - which is itself a bug or at least undesirable.
            f = new Frame1("Packed Frame 2");
            f.pack(); // ONLY DIFFERENCE IS PACK!!!
            f.show();
            f.dispose();
            f = null;

            bigGC();
            System.out.println("Finished packExample.");
          }


          /*
             This example demonstrates the fact that when a frame is disposed
             its menus are not disposed. The result of this is that the frame,
             which is an ActionListener for the menus, will not be GCed. In turn,
             every object referred to by the frame will not be GCed. This can be
             a great deal of memory. I would consider this a major core leak.

             Note: Similar to the packing problem shown above, the last Frame
             with a MenuBar set will not be GCed (this is left to the reader
             to verify). So two dummy frames are created with MenuBars to
             assure that the Frame of interest can be GCed - although it is not
             which is the bug we are demonstrating.
           */
          public static void menuExample() {
            Frame1 f = null;

            System.out.println("Running menuExample.");

            // This frame will not be GCed because its menu refers to it as
            // an ActionListener
            f = new Frame1("Packed Frame with Menu");
            MenuBar mb = new MenuBar();
            f.setMenuBar(mb);
            Menu m = new Menu("A Menu");
            mb.add(m);
            m.addActionListener(f);
            f.pack();
            f.show();
            f.dispose();
            mb = null;
            m = null;
            f = null;

            // This Frame will be GCed after the next Frame with a MenuBar is created
            f = new Frame1("Dummy Packed Frame with Menu 1");
            mb = new MenuBar();
            f.setMenuBar(mb);
            f.pack();
            f.show();
            f.dispose();
            mb = null;
            f = null;

            // This Frame will not be GCed because it is the last Frame with
            // a MenuBar created - which is itself a bug or at least undesirable.
            f = new Frame1("Dummy Packed Frame with Menu 2");
            mb = new MenuBar();
            f.setMenuBar(mb);
            f.pack();
            f.show();
            f.dispose();
            mb = null;
            f = null;

            bigGC();
            System.out.println("Finished menuExample.");
          }


          /*
             This example demonstrates the less than ideal workaround of Frame2.

             Note: Similar to the packing problem shown above, the last Frame
             with a MenuBar set will not be GCed (this is left to the reader
             to verify). So two dummy frames are created with MenuBars
             to assure that the Frame of interest can be GCed.
           */
          public static void menuWorkaroundExample() {
            Frame2 f = null;

            System.out.println("Running menuWorkaroundExample.");

            // This Frame will be GCed because the Frame2 class cleans up the
            // Menus before disposing the Frame.
            f = new Frame2("Packed Frame with Menu");
            MenuBar mb = new MenuBar();
            f.setMenuBar(mb);
            Menu m = new Menu("A Menu");
            mb.add(m);
            m.addActionListener(f);
            f.pack();
            f.show();
            f.dispose();
            mb = null;
            m = null;
            f = null;

            // This Frame will be GCed after the next Frame with a MenuBar is created
            f = new Frame2("Dummy Packed Frame with Menu 1");
            mb = new MenuBar();
            f.setMenuBar(mb);
            f.pack();
            f.show();
            f.dispose();
            mb = null;
            f = null;

            // This Frame will not be GCed because it is the last Frame with
            // a MenuBar created - which is itself a bug or at least undesirable.
            f = new Frame2("Dummy Packed Frame with Menu 2");
            mb = new MenuBar();
            f.setMenuBar(mb);
            f.pack();
            f.show();
            f.dispose();
            mb = null;
            f = null;

            bigGC();
            System.out.println("Finished menuWorkaroundExample.");
          }


          // To be fairly certain that the garbage collector is caught
          // up with the text output
          public static void bigGC() {
            int n = 10;
            try {
              while (n-- > 0) {
        Thread.currentThread().sleep(1000);
        System.gc();
              }
            }
            catch (Exception e) {
            }
          }


          public static void main(String args[]) {
            // Examples - only one should be run at a time; comment out the others
            packExample();
            // menuExample();
            // menuWorkaroundExample();


            // A final sanity check to make sure the problem is not related
            // to scoping. No output should be generated between the end of
            // the example and the final print statement below.
            bigGC();
            System.out.println("Scope check finished.");
          }

        }


        class Frame1 extends Frame implements ActionListener {
          public Frame1(String title) {
            super(title);
          }

          public void actionPerformed(ActionEvent event) {
          }

          public void finalize() {
            System.out.println("GCExample: Frame1 being GCed = " + this.getTitle());
          }
        }



        /*
          Frame 2 implements a less than ideal workaround for the menu GC problem
          by overriding the dispose method of a frame.
         */

        class Frame2 extends Frame implements ActionListener {
          public Frame2(String title) {
            super(title);
          }

          public void actionPerformed(ActionEvent event) {
          }


          // Override the dispose method to call cleanMenus first
          public void dispose() {
            cleanMenus();
            super.dispose();
          }


          // Clean the menus by iterating through each menu item and removing
          // any ActionListeners for that menu item
          protected void cleanMenus() {
            Vector listeners = new Vector();
            findActionListeners(listeners, this);

            MenuBar mb = getMenuBar();
            if (mb != null) {
              for (int i = 0; i < mb.getMenuCount(); i++) {
        Menu m = mb.getMenu(i);
        removeActionListenersFromMenuItem(m, listeners);
        m.removeAll();
              }
            }
          }


          // Recursively search for the ActionListener instances in the
          // component heirarchy
          private void findActionListeners(Vector v, Component c) {
            if (c instanceof ActionListener) {
              v.addElement(c);
            }
            if (c instanceof Container) {
              for (int i = 0; i < ((Container)c).getComponentCount(); i++) {
        findActionListeners(v, ((Container)c).getComponent(i));
              }
            }
            return;
          }


          // Recursively remove all ActionListener instances from each MenuItem
          private void removeActionListenersFromMenuItem(MenuItem mi, Vector v) {
            for (int i = 0; i < v.size(); i++) {
              // System.out.println("Removing ActionListener(" + v.elementAt(i) +
              // ") from MenuItem(" + mi + ")");
              mi.removeActionListener((ActionListener)v.elementAt(i));
            }
            if (mi instanceof Menu) {
              for (int j = 0; j < ((Menu)mi).getItemCount(); j++) {
        removeActionListenersFromMenuItem(((Menu)mi).getItem(j), v);
              }
            }
          }


          public void finalize() {
            System.out.println("GCExample: Frame2 being GCed = " + this.getTitle());
          }
        }
        ======================================================================
        [chamness 11/17/97]

        Under 1.1.4, the following test code will hang after 1200 interations on
        a win_95 system. It slows my Solaris 2.5.1 machine to the point
        of being unresponsive. There is no problem when running under 1.0.2
        on Solaris.
        ----------------------------------------------------------------------
        import java.awt.*;

        class tester
        {
        public static void main(String args[])
        {
        int count=0;
        while(true)
        {
        System.out.println(count++);
        Frame f= new Frame("my Frame");
        f.pack();
        }
        }
        }
        ----------------------------------------------------------------------
        Part of the Solaris output using -verbosegc:

        <GC: init&scan: 13 ms, scan handles: 1332 ms, sweep: 0 ms, compact: 0 ms>
        <GC(async, interrupted): freed 0 objects, 0 bytes in 1306 ms, 21% free (2926448/13315272)>
        <GC: init&scan: 12 ms, scan handles: 1294 ms, sweep: 0 ms, compact: 0 ms>
        <GC(async, interrupted): freed 0 objects, 0 bytes in 1297 ms, 21% free (2926448/13315272)>
        <GC: init&scan: 12 ms, scan handles: 1285 ms, sweep: 0 ms, compact: 0 ms>
        <GC(async, interrupted): freed 0 objects, 0 bytes in 1283 ms, 21% free (2926448/13315272)>
        <GC: init&scan: 12 ms, scan handles: 1271 ms, sweep: 0 ms, compact: 0 ms>
        <GC(async, interrupted): freed 0 objects, 0 bytes in 1273 ms, 21% free (2926448/13315272)>
        <GC: init&scan: 12 ms, scan handles: 1261 ms, sweep: 0 ms, compact: 0 ms>
        <GC(async, interrupted): freed 0 objects, 0 bytes in 1465 ms, 21% free (2926448/13315272)>
        <GC: init&scan: 16 ms, scan handles: 1449 ms, sweep: 0 ms, compact: 0 ms>
        <GC(async, interrupted): freed 0 objects, 0 bytes in 1335 ms, 21% free (2926448/13315272)>
        <GC: init&scan: 12 ms, scan handles: 1323 ms, sweep: 0 ms, compact: 0 ms>


        Attachments

          Issue Links

            Activity

              People

                pongsunw Patrick Ong (Inactive)
                johsunw Joon Oh (Inactive)
                Votes:
                0 Vote for this issue
                Watchers:
                0 Start watching this issue

                Dates

                  Created:
                  Updated:
                  Resolved:
                  Imported:
                  Indexed: