JDK-6773985 : OutOfMemory (PermGen space) under Linux / Firefox when switching bw. applets
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 6
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: linux
  • CPU: x86
  • Submitted: 2008-11-20
  • Updated: 2011-05-18
  • Resolved: 2011-05-18
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.
6u21Fixed 7 b48Fixed
java version "1.6.0_06"
Java(TM) SE Runtime Environment (build 1.6.0_06-b02)
Java Hotspot(TM) Client VM (build 10.0-b22, mixed mode, sharing)

java version "1.6.0_10"
Java(TM) SE Runtime Environment (build 1.6.0_10-b33)
Java HotSpot(TM) Client VM (build 11.0-b15, mixed mode, sharing)

Linux 2.6.13-15.8-smp x86_64

Reproduce with Mozilla 1.7.11 and Firefox 1.0.6.
My colleages reproduced with Firefox 2 and Firefox 3.

If I open the web browser (firefox or mozilla) and load Java applets one by one, it eventually runs out of memory (PermGen space). The reason is that it does not release old classloaders of applets that are not anymore active, and hence does not release the classes that were allocated by this classloader.

If I have more than 5 applets and browse them one by one,  and after the last applet I come back to the first applet, I see that its classes are reloaded again by a new classloader, even though the classes of this applet are still in the VM hold by an old classloader.

The increase of the code memory can easily be observed by using jconsole.

I analyzed the situation with jhat, and it seems all class loaders are kept in a HashMap in a static field of sun.awt.X11.XToolkit.winToDispatcher. This is of course a memory leak, since when the class loader is hold, all applet classes loaded by the classloader are also hold and never freed by the Garbage collection.

The exact reference chain (shown by jhat) is this:

Static reference from sun.awt.X11.XToolkit.winToDispatcher (from class sun.awt.X11.XToolkit) :
--> java.util.HashMap@0x79d7ce38 (40 bytes) (field table:)
--> [Ljava.util.HashMap$Entry;@0x79d98670 (72 bytes) (Element 8 of [Ljava.util.HashMap$Entry;@0x79d98670:)
--> java.util.HashMap$Entry@0x79e75490 (24 bytes) (field value:)
--> java.util.Vector@0x79e76220 (24 bytes) (field elementData:)
--> [Ljava.lang.Object;@0x79e77450 (48 bytes) (Element 0 of [Ljava.lang.Object;@0x79e77450:)
--> sun.awt.X11.XEmbedClientHelper@0x79e77438 (22 bytes) (field embedded:)
--> sun.awt.X11.XEmbeddedFramePeer@0x79e760b8 (301 bytes) (field target:)
--> sun.plugin.viewer.frame.XNetscapeEmbeddedFrame@0x79e75618 (361 bytes) (field appContext:)
--> sun.awt.AppContext@0x79e755e0 (49 bytes) (field contextClassLoader:)
--> sun.plugin.security.PluginClassLoader@0x79e74ac0 (123 bytes)

All class loaders are hold by such a chain.

Indeed the XEmbedClientHelper.install() adds itself to the XToolkit but never removes itself. I think it should remove itself when the applet is destroyed. The XEmbedClientHelper has a link chain to the AppContext, which holds the class loader.

In principle, it can be reproduced by creating a couple of large applets (with lots of classes) and load them one by one into the webbrowser without shutting the webbrowser down. The increase in code can be watched with jconsole. If there are many applets, you will eventually notice the OutOfMemory (PermGen space) no matter how high your PermGenSize setting is.

For your conveniance, here a csh script that generated 6 large applets,
each with 1000 classes:

---------------------------- cut -------------------------------------------

mkdir a
mkdir b
mkdir c
mkdir d
mkdir e
mkdir f

foreach d (a b c d e f)

	pushd .
	cd $d
	touch MyApplet.java
	echo "package "$d";" >> MyApplet.java
	echo "import java.awt.*;" >> MyApplet.java
	echo "import java.awt.event.*;" >> MyApplet.java
	echo "import javax.swing.*;" >> MyApplet.java
	echo "public class MyApplet extends JApplet {" >> MyApplet.java
	echo "  public void init() {" >> MyApplet.java
	echo "    super.init();" >> MyApplet.java
	echo '    JButton helpButton = new JButton("Test");' >> MyApplet.java
	echo "    helpButton.addActionListener(new ActionListener() {" >> MyApplet.java
	echo "      public void actionPerformed(ActionEvent evt) {" >> MyApplet.java
	echo '        System.err.println("Help pressed"); ' >> MyApplet.java
	echo "      }});" >> MyApplet.java
	echo "    JPanel panel = new JPanel(new FlowLayout());" >> MyApplet.java
	echo "    panel.add(helpButton);" >> MyApplet.java
	echo "    getContentPane().add(panel);" >> MyApplet.java

	foreach i (0 1 2 3 4 5 6 7 8 9)
	foreach j (0 1 2 3 4 5 6 7 8 9)
	foreach k (0 1 2 3 4 5 6 7 8 9)
	        echo "    new Class"$i$j$k"();" >> MyApplet.java
		touch Class$i$j$k.java
		echo "package "$d";" >> Class$i$j$k.java
		echo "public class Class"$i$j$k >> Class$i$j$k.java
		echo "{" >> Class$i$j$k.java
		foreach m (0 1 2 3 4 5 6 7 8 9)
	        foreach n (0 1 2 3 4 5 6 7 8 9)
			echo "  public void method"$m$n"() {}" >> Class$i$j$k.java
		echo "}" >> Class$i$j$k.java

	echo "  }" >> MyApplet.java
	echo "}" >> MyApplet.java

--------------------- cut ----------------------------------------

Run this script in Linux or Unix creates directories a b c d e f which contains the applet 6 times. The only difference is that package name.
Compile these one by one and each package into one jar:

javac -d . src/a/*.java
javac -d . src/b/*.java
javac -d . src/c/*.java
javac -d . src/d/*.java
javac -d . src/e/*.java
javac -d . src/f/*.java
jar cf appletA.jar a/
jar cf appletB.jar b/
jar cf appletC.jar c/
jar cf appletD.jar d/
jar cf appletE.jar e/
jar cf appletF.jar f/

Now you have 6 applets, each just containing one button.
The applet creates instances of 1000 classes, just to make sure 1000 classes are loaded. The instances are garbage collectable.

Now create 6 HTML files that load the applets.
Here is one (index0.html):

----------------- cut here -------------------------------------------------
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<BODY bgcolor="#FFFFFF">
    classid = "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
    codebase = "http://java.sun.com/update/1.4.2/jinstall-1_4-windows-i586.cab#Version=1,4,0,0"
    WIDTH = "300" HEIGHT = "40" ALIGN = "baseline" >
    <PARAM NAME = CODE VALUE = "a.MyApplet" >
    <PARAM NAME = ARCHIVE VALUE = "appletA.jar" >
    <PARAM NAME = "type" VALUE = "application/x-java-applet;version=1.4">
    <PARAM NAME = "scriptable" VALUE = "false">

            type = "application/x-java-applet;version=1.4"             CODE = "a.MyApplet"             JAVA_CODEBASE = "."             ARCHIVE = "appletA.jar"             WIDTH = "300"             HEIGHT = "40"             ALIGN = "baseline" 	    scriptable = false 	    pluginspage = "http://java.sun.com/products/plugin/index.html#download">

<APPLET CODE = "a.MyApplet" JAVA_CODEBASE = "." ARCHIVE = "appletA.jar" WIDTH = "300" HEIGHT = "40" ALIGN = "baseline">




<A HREF="index1.html">NEXT</A>

----------------------------------- cut here -------------------------------

Create similar index1.html, index2.html ... index5.html.
Each one should have the NEXT link pointing to the next one,
and index5.html should have the NEXT link pointing to index0.html.
Each should load a different applet, i.e.
index1.html loads b.MyApplet  in appletB.jar
index2.html loads c.MyApplet in appletC.jar
index5.html loads f.MyApplet in appletF.jar.

Now open index0.html, wait until the applet is loaded,
then repeat to click NEXT, each click loads a new applet,
until OutOfMemory. If you don't want to wait until OutOfMemory,
use jconsole to see the code memory increase for each applet
that is loaded.

When I tried this with only 3 applets, the classLoaderCache
of the plugin avoided that the classes of an applet are reloaded.
In this case, hit the x key in the Java Console before clicking the
NEXT link to force that an previously loaded applet is reloaded.

On my machine, with 6 applets, when coming from the last applet
to the first applet, it is reloaded independent of hitting the x key.

When an applet is destroyed, then all its memory should be
freed. The applet classloader should not be hold by a reference
chain from XToolkit.

Or to say it differently:
If I have 100 large applets that individually are small enough to
run in the brower without OutOfMemory, then it should be possible
to browse through all 100 large applets without shutting down
the browser and without OutOfMemory caused by accumulated

OutOfMemory (PermGen space)

OutOfMemory (PermGen space)

This bug can be reproduced always.

---------- BEGIN SOURCE ----------
See Description. It contains a csh script to generate 6 large applets.
---------- END SOURCE ----------

Shutting down the browser before loading the next applet avoids the OutOfMemory.

SUGGESTED FIX In a few words: override XEmbeddedFramePeer.dispose() and remove the corresponding XEmbedClientHelper from XToolkit's event dispatchers map. See http://sa.sfbay.sun.com/projects/awt_data/7/6773985/ for details.

EVALUATION The reference chain from Applet2ClassLoader to the GC root sun.awt.X11.XToolkit.winToDispatcher also hold loaded classes from being GCed in the new plugin. Attached a screenshot of the reference chain when run test in the new plugin. Assign to AWT team.