FULL PRODUCT VERSION : java version "1.6.0_01" Java(TM) SE Runtime Environment (build 1.6.0_01-b06) Java HotSpot(TM) Client VM (build 1.6.0_01-b06, mixed mode, sharing) java version "1.5.0_11" Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_11-b03) Java HotSpot(TM) Client VM (build 1.5.0_11-b03, mixed mode, sharing) ADDITIONAL OS VERSION INFORMATION : Microsoft Windows XP [Version 5.1.2600] Linux rh3 2.4.21-32.ELsmp #1 SMP Fri Apr 15 21:17:59 EDT 2005 i686 i686 i386 GNU/Linux A DESCRIPTION OF THE PROBLEM : In default L&F (metal), each call of JScrollPane.updateUI() will make it's propertyChangeListener list grow. The leaked class is: javax.swing.plaf.metal.MetalScrollPaneUI$1, which is created in method javax.swing.plaf.metal.MetalScrollPaneUI.createScrollBarSwapListener(). It can be reproduced with both J2SE 1.5 and 1.6, on both Windows and Linux platform. In my opinion, the root cause should be: javax.swing.plaf.metal.MetalScrollPaneUI extends javax.swing.plaf.basic.BasicScrollPaneUI, but the method "void uninstallListeners(JScrollPane scrollPane)" in MetalScrollPaneUI does not override the method "void uninstallListeners(JComponent c)" in BasicScrollPaneUI, so the listener registered in MetalScrollPaneUI does not be unregistered when uninstallUI is called. STEPS TO FOLLOW TO REPRODUCE THE PROBLEM : It is simple to reproduce. In any Swing program, call updateUI() method of a JScrollPane object, and then check it's property change listener list with method getPropertyChangeListeners(), you will see the listener list grows each time. In my sample source code, it also print out the class name of the listeners. REPRODUCIBILITY : This bug can be reproduced always. ---------- BEGIN SOURCE ---------- package test; import java.awt.*; import javax.swing.*; import java.awt.event.*; import java.beans.PropertyChangeListener; import java.util.*; public class UpdateTest extends JDialog { javax.swing.Timer timer; JButton exitBtn = new JButton(); JScrollPane jScrollPaneTest = new JScrollPane(); JLabel jLabelInfo = new JLabel(); public static void main(String[] args) { UpdateTest dlg = new UpdateTest(); dlg.setVisible(true); } public UpdateTest() { setTitle("UpdateTest"); exitBtn.setText("exit"); exitBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); } }); jLabelInfo.setText("Test JScrollPane.updateUI, please see stdout."); jScrollPaneTest.getViewport().add(jLabelInfo); getContentPane().setLayout(new BorderLayout()); getContentPane().add(exitBtn, java.awt.BorderLayout.SOUTH); getContentPane().add(jScrollPaneTest, java.awt.BorderLayout.CENTER); pack(); timer = new javax.swing.Timer(1000, new ActionListener() { public void actionPerformed(ActionEvent e) { JComponent toUpdate = jScrollPaneTest; toUpdate.updateUI(); PropertyChangeListener[] listeners = toUpdate.getPropertyChangeListeners(); // print it's PropertyChangeListeners after updateUI. System.out.println("There are " + listeners.length + " PropertyChangeListeners."); HashMap<String, Integer> h = new HashMap<String, Integer>(); for (int i = 0; i < listeners.length; ++i) { String className = listeners[i].getClass().getName(); int n = 1; if (h.containsKey(className)) { n = h.get(className) + 1; } h.put(className, n); } for (Iterator<Map.Entry<String, Integer>> it = h.entrySet().iterator(); it.hasNext(); ) { Map.Entry<String, Integer> entry = it.next(); System.out.println("" + entry.getValue() + " listeners are " + entry.getKey()); } System.out.println(); } }); timer.start(); } } ---------- END SOURCE ----------
|