Applets and memory not being released by Java Plug-in

Hi.

I am experiencing a strange memory-management behavior of the Java Plug-in with Java Applets. The Java Plug-in seems not to release memory allocated for non-static member variables of the applet-derived class upon destroy() of the applet itself.

I have built a simple "TestMemory" applet, which allocates a 55-megabytes byte array upon init(). The byte array is a non-static member of the applet-derived class. With the standard Java Plug In configuration (64 MB of max JVM heap space), this applet executes correctly the first time, but it throws an OutOfMemoryException when pressing the "Reload / Refresh" browser button or if pressing the "Back" and then the "Forward" browser buttons. In my opionion, this is not an expected behavior. When the applet is destroyed, the non-static byte array member should be automatically invalidated and recollected. Isn't it?

Here is the complete applet code:

// ===================================================

import java.awt.*;

import javax.swing.*;

publicclass TestMemoryextends JApplet

{

private JLabel label =null;

privatebyte[] testArray =null;

// Construct the applet

public TestMemory()

{

}

// Initialize the applet

publicvoid init()

{

try

{

// Initialize the applet's GUI

guiInit();

// Instantiate a 55 MB array

// WARNING: with the standard Java Plug-in configuration (i.e., 64 MB of

// max JVM heap space) the following line of code runs fine the FIRST time the

// applet is executed. Then, if I press the "Back" button on the web browser,

// then press "Forward", an OutOfMemoryException is thrown. The same result

// is obtained by pressing the "Reload / Refresh" browser button.

// NOTE: the OutOfMemoryException is not thrown if I add "testArray = null;"

// to the destroy() applet method.

testArray =newbyte[55 * 1024 * 1024];

// Do something on the array...

for (int i = 0; i < testArray.length; i++)

{

testArray[i] = 1;

}

System.out.println("Test Array Initialized!");

}

catch (Exception e)

{

e.printStackTrace();

}

}

// Component initialization

privatevoid guiInit()throws Exception

{

setSize(new Dimension(400, 300));

getContentPane().setLayout(new BorderLayout());

label =new JLabel("Test Memory Applet");

getContentPane().add(label, BorderLayout.CENTER);

}

// Start the applet

publicvoid start()

{

// Do nothing

}

// Stop the applet

publicvoid stop()

{

// Do nothing

}

// Destroy the applet

publicvoid destroy()

{

// If the line below is uncommented, the OutOfMemoryException is NOT thrown

// testArray = null;

}

//Get Applet information

public String getAppletInfo()

{

return"Test Memory Applet";

}

}

// ===================================================

Everything works fine if I set the byte array to "null" upon destroy(), but does this mean that I have to manually set to null all applet's member variables upon destroy()? I believe this should not be a requirement for non-static members...

I am able to reproduce this problem on the following PC configurations:

* Windows XP, both JRE v1.6.0 and JRE v1.5.0_11, both with MSIE and with Firefox

* Linux (Sun Java Desktop), JRE v1.6.0, Mozilla browser

* Mac OS X v10.4, JRE v1.5.0_06, Safari browser

Your comments would be really appreciated.

Thank you in advance for your feedback.

Regards,

Marco.

[6223 byte] By [neologicaa] at [2007-11-26 19:01:38]
# 1

Hi Marco,

googled a bit around this and noticed on this page:

http://java.sun.com/developer/community/askxprt/2006/jl0227.html

that there is something called "legacy-lifecycle" which has to do with what html tags you are using when embedding the applet in your page. So you might want to post the html code as well, because it may be relevant. Or not.

br Kusti

nyholkua at 2007-7-9 20:46:29 > top of Java-index,Desktop,Deploying...
# 2

Marco, from the previous discussion in http://forums.java.net/jive/thread.jspa?threadID=23250&tstart=0 you were quite right that I had set max memory on Unix as -Xmx320m - when testing Java3D in an applet.Upon re-setting to the default, all platforms tested now throw OutOfMemoryError.

A further point - if you override "void finalize()" in your code and print something there as well as in "void destroy()", then with sufficient -Xmx memory, when you refresh the page, you will see finalize is not being called as often as destroy.Actually, it takes 2 refreshes before finalize is called.So something in the Java VM or Plugin is hanging on to at least two Applet instances before they begin to be disposed.

In view of this, perhaps the workaround you found is pointing to the correct solution - effectively introducing a "void dispose" method for applets?I agree with you, this is not expected behaviour, and is certainly non-documented.

-- Russell

RussellEasta at 2007-7-9 20:46:30 > top of Java-index,Desktop,Deploying...
# 3

This is the HTML code Marco posted - there is no legacy lifecycle option in it.

<html>

<head>

<title>Test Memory Applet</title>

</head>

<body bgcolor="#cccccc" topmargin="0" leftmargin="0">

<applet align="middle" vspace="0" hspace="0" height="100%" width="100%" name="Test Memory Applet" archive="TestMemory.jar" code="TestMemory.class" codebase=".">

<PARAM value="application/x-java-applet;version=1.5" NAME="type">

<PARAM value="false" NAME="scriptable">

</applet>

</body>

</html>

RussellEasta at 2007-7-9 20:46:30 > top of Java-index,Desktop,Deploying...
# 4

Hi Russel,

thank you for your message.

Your suggestion about logging the finalize() method is very useful indeed:

From my tests on Windows XP, it looks like the finalize() method related to the FIRST applet instance is only called AFTER the OutOfMemoryException related to the SECOND load is thrown. In my opinion, this is abnormal.

Also, if I load the applet for the first time, then I press the "Back" browser button, the destroy() method is called, but finalize() if not called on the applet. This may also be normal, but in any case finalize() on the first applet instance should be called BEFORE throwing the OutOfMemoryException on the second applet execution, since at that point the memory related to the first applet instance could already be recollected...

Perhaps we should file a bug to Sun... What do you think?

Best regards,

Marco.

neologicaa at 2007-7-9 20:46:30 > top of Java-index,Desktop,Deploying...
# 5

Well I'm not sure it is "abnormal" but it is certainly weird, and certainly there is nothing guaranteed about when the finalize method gets called. The only thing I can think of is that the Plugin or VM is maybe caching the most recently used applets for whatever reason.In this way, there would still be a strong ref held on those applets and thus finalize never called. It would be good to file a bug report.

RussellEasta at 2007-7-9 20:46:30 > top of Java-index,Desktop,Deploying...
# 6

Hi Russel,

thank you for your reply.

The reason why I am saying it is an "abnormal" behavior is that the documentation says that the Garbage Collector must perform every effort to free unused memory before throwing an OutOfMemoryException. In our test case, the memory related to an "old" applet instance is in my opinion unused memory, which should be recollected before throwing an OutOfMemoryException.

Your point about a possible reference to the previous applet instance for caching purposes makes a lot of sense.

If you agree with me that this is a bug or at least an undesired behavior, I will file a bug report asap.

As far as a possible workaround is concerned, do you think that setting to "null" all member variables of the applet class is enough or not? Will ALL objects created by the applet-derived class and by other related classes be recollected in this case in your opinion? Certainly this is an incredible effort in some complex cases... And what about all Swing components of the applet's GUI? Will they be recollected or not?

My certainties about garbage collection are trembling...

Thank you.

Marco.

neologicaa at 2007-7-9 20:46:30 > top of Java-index,Desktop,Deploying...
# 7
yes, definitely worth trembling about. ;-)Also it is worth a bug report.-- Russell
RussellEasta at 2007-7-9 20:46:30 > top of Java-index,Desktop,Deploying...
# 8

Hi Marco,

I think what you are seeing here is propably not a bug but poorly documented feature of the applet / spec.

The spec says that the life cycle of an applet goes like:

"init, N x (start/stop), destroy", but it does not promise to release the applet object at any time. Naturally a programmer creating applets is inclined to think that sure it will be release asap. But there are propably good reasons for the browser / plugin to keep one or more references to the applet around, and seems there is nothing in the spec that says it shouln't. On the contrary the spec says explicitly about destroy: "to inform this applet that it is being reclaimed and that it should destroy any resources ". I my view this implies that you should also release all references to all objects (at least if they use a lot of memory). They could have been more explicit here.

As to garbage collection, I think there can be no doubt that it works as adverticed. The 'problem' is that the browser/plugin is not releasing the reference to the applet. And as pointed out, there is no promise that it does, although from applet writer point of view that might be desirable, I'm confident that from browser write point of view the opposite is propably true.

So, to set the references to null in destroy is the way to workaround this non-bug ;-)

As to finalizer, the spec is explicit in that there is no guarantee that it will ever be called as there is no guarantee that all objects will be ever collected. Even then there is no guarantee that even if the finalizer gets called the object will be garbage collected as these happen in separate phases and there may be no need to reclaim that object or the finalizer may create references to that object which makes it non-garbage.

In my view finalizer are too difficult to use, even for debugging purposes like this, as it is too easy to get the wrong conclusion. Just thought this would be worth mentioning, not implying that this is what has happened in this discussion here.

Come to think of it, but could not not confirm it, the system could conceivably run out of memory without claiming all garbage if the system sees that collecting all garbage would not free enough memory to full fill the request, so the system might vote to fail early!

br Kusti

nyholkua at 2007-7-9 20:46:31 > top of Java-index,Desktop,Deploying...
# 9

Hi Kusti,

thank you for your message: your point of view is interesting and certainly contains some truths.

It is probably true that the Java Plug-in is maintaing some references to the previous applet instances, and this prevents the applet itself from being garbage collected. But I believe that it is a least weird that the JPI is not able to release and recollect the previous applet instance in a moment (the second load of the applet) when all "user / programmer" 's references to that instance are actually lost.

Please notice that in our test case the second instance of the applet would be able to execute normally (i.e., without the OutOfMemoryException) if the memory related to the first applet instance was released and recollected.

By the way, do you have ideas about why the JPI should maintain references to a destroyed applet instance? It looks like there is no way for the user to restore a specific applet instance after having destroyed it (even pressing Back, then Forward on the browser would create a different applet instance...).

Your comments are appreciated.

Regards,

Marco.

neologicaa at 2007-7-9 20:46:31 > top of Java-index,Desktop,Deploying...
# 10

Hi Marco,

my guess as to why JPI would keep references around, if it does keep them, is that it propably is an implementation side effect. A lot of things are cached in the name of performance and it is easy to leave things laying around in your cache. Maybe the page with the associated images/applets is kept in the browser cache untill the browser needs some memory and if the browser memory manager is not co-operating with the JPI/JVM memory manager the browser is not out of memory, thus not releasing its caches but the JVM may be out of memory. Thus the browser indirectly keeps the reference that it realy does not need. This reference could be inderect through some 'applet context' or what ever the browser uses to interact with JPI, don't realy know any of these details, just imaging what must/could be going on there. Browser are amazingly complicated beast.

This behaviour that you are observing, weather the origin is something like I speculated or not, is not nice but I would not expect it to be fixed even if you filed a bug report. I guess we are left with relleasing all significatn memory structures in destroy. A simple way to code this is not to store anything in the member fields of the applet but in a separate class; then one has to do is to null that one reference from the applet to that class in the destroy method and everything will be relased when necessary. This way it is not easy to forget to release things.

Hey, here is a simple, imaginary, way in which the browser could cause this problem:

The browser, of course needs a reference to the applet, call it m_Applet here. Presume the following helper function:

Applet instantiateAndInit(Class appletClass) {

Applet applet=appletClass.newInstance();

applet.init();

return applet;

}

When the browser sees the applet tag it instantiates and inits the new applet as follows:

m_Applet=instantiateAndInit(appletClass);

As you can readily see, the second time the instantiation occurs, the m_Applet holds the reference to the old applet until *after* the new instance is created and initlized. This would not cause a memory leak but would require that twice the memory needed by the applet would be required to prevent OutOfMemory.I guess it is not fair to call this sort of thing a bug but it is questionable design.In real life this is propably not this blatant, but could happen You could try, if you like, by allocating less than 32 Megs in your init. If you then do not run out of memory it is an indication that there are at most two instances of your applet around and thus it could well be someting like I've speculated here.

br Kusti

nyholkua at 2007-7-9 20:46:31 > top of Java-index,Desktop,Deploying...
# 11

Hi Kusti,

thanks again for your reply.

I agree with everything you've said in your last post. Also, your guess about the way the browser / applet interaction may work makes sense.

I am developing the new version of my applet using a kind of "document" class, which will contain all main data used by the application. The main applet class will have a reference to an instance of such a "document" class, and will set this reference to "null" upon destroy.

This should prevent most memory problems on reload of the applet.

In any case, yesterday I posted a bug report on this issue: I believe at least this behavior should be better documented.

Thanks again for all your feedback.

Best regards,

Marco.

neologicaa at 2007-7-9 20:46:31 > top of Java-index,Desktop,Deploying...
# 12

"By the way, do you have ideas about why the JPI should maintain references to a destroyed applet instance? It looks like there is no way for the user to restore a specific applet instance after having destroyed it (even pressing Back, then Forward on the browser would create a different applet instance...)."

Is this true of all browsers? In the past, different browsers called init() start()/stop() destroy() on different actions.

Are all browsers interacting with the plugin the same way these days?

It seems to me that a user hitting the back button to go back to a page with an applet that might have been a candidate for destroy() might want to go back to the applet -- fully loaded and in the same state as when the user went to a different page.

Knowing exactly when to destroy an applet seems like another version of the Stopping Problem -- Yes? No?

kenwarnera at 2007-7-9 20:46:31 > top of Java-index,Desktop,Deploying...
# 13

I think that was the useful information that Kusti provided earlier, regarding "legacy lifecycle". Here is a snip from the web page he found:

The default applet life cycle model in Java Plug-in is to call init() and start() during every page load, and call stop() and destroy() during every page switch. No applet instance lives beyond the lifetime of the HTML page that hosts the applet. However, you could select to use the legacy life cycle. Legacy life cycle means that init() and start() are called when the applet is loaded first, and during page switch only stop()/start() is called. The OBJECT/EMBED's boolean HTML parameter to enable this is called "legacy-lifecycle".

RussellEasta at 2007-7-9 20:46:31 > top of Java-index,Desktop,Deploying...