United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-6786318 MemoryCache leaks memory on repetative requests for the same resource
JDK-6786318 : MemoryCache leaks memory on repetative requests for the same resource

Details
Type:
Bug
Submit Date:
2008-12-17
Status:
Closed
Updated Date:
2010-09-17
Project Name:
JDK
Resolved Date:
2009-01-26
Component:
deploy
OS:
generic,windows_xp
Sub-Component:
deployment_toolkit
CPU:
x86,generic
Priority:
P3
Resolution:
Fixed
Affected Versions:
6,6u10
Fixed Versions:
6u12 (b04)

Related Reports
Backport:
Duplicate:
Relates:

Sub Tasks

Description
If you, for example, try running the SmokeParticles sample from our public demo collection (http://javafx.com/samples/SmokeParticles/index.html) and let it sit
in the browser for a while (you're reading the article), it will bring
your system to its knees (or just keep burning all the spare CPU cycles,
depending on how good your OS scheduler is).

The problem is a memory leak in the JNLP infrastructure, namely in MemoryCache,
together with this innocent and simple pattern used in the demo:
return ImageView {
    image : Image { url: "{__DIR__}/../resources/texture.png" }
};

that is, creating (and properly freeing) many ImageViews from the same resource. 

The MemoryCache thing is unnecessarily complicated, it seems. It implements
a reference counting scheme, trying to be smarter than the garbage collector
and failing at it. Each call to getLoadedResource with the same (in fact equal,
not the same) String leaks a CachedResourceReference and the mentioned String.
(Please note that each time a new String instance is used, as it is constructed
along the code path):
com.sun.deploy.cache.MemoryCache.getLoadedResource(MemoryCache.java:78)
com.sun.deploy.net.DownloadEngine.getResourceCacheEntry(DownloadEngine.java:1483)
com.sun.deploy.net.DownloadEngine.getResourceCacheEntry(DownloadEngine.java:1436)
com.sun.deploy.net.DownloadEngine.getCachedJarFile(DownloadEngine.java:457)
com.sun.deploy.net.DownloadEngine.getCachedJarFile(DownloadEngine.java:434)
com.sun.jnlp.JNLPClassLoader$3.run(JNLPClassLoader.java:434)
java.security.AccessController.doPrivileged
com.sun.jnlp.JNLPClassLoader.getJarFile(JNLPClassLoader.java:430)
com.sun.jnlp.JNLPCachedJarURLConnection.connect(JNLPCachedJarURLConnection.java:160)
com.sun.jnlp.JNLPCachedJarURLConnection.getInputStream(JNLPCachedJarURLConnection.java:203)
java.net.URL.openStream
com.sun.javafx.scene.image.ImageLoader.readImage
com.sun.javafx.scene.image.ImageLoader.<init>
javafx.scene.image.Image.initialize$impl
javafx.scene.image.Image.initialize
javafx.scene.image.Image.postInit$
javafx.scene.image.Image.initialize$
particles.Particle.create$impl(Particle.fx:33)
particles.Particle.create(Particle.fx:20)
[...] 

I have a dump from the point where the GC was scraping for the last free bits
out of 64MB default heap. Bzipped it is 8.5MB.
But it boils down to (for single resource):
MemoryCache.loadedResource map having an entry (LoadedResourceReference)
strong*1 holding the object (CacheEntry) and a big set of WeakReferences
(CachedResourceReferences) to the same CacheEntry.
MemoryCache was probably intended to remove the entry from the map
once all the WeakReferences die, but:
1. that would never happen (because of *1)
2. all would die at once anyway, no need to have 200.000 of them.
Simplified testcase:

import com.sun.deploy.cache.MemoryCache;

public class Test {
    public static void main(String[] args) throws Exception {
        String u = "http://localhost/";
        Object o = new int[100000]; //make it large to detect object leak
        Object junk;

        System.out.println("Total Memory"+Runtime.getRuntime().totalMemory());
        MemoryCache.addLoadedResource(u, o);
        for(int i=0; i<1000000; i++) {
            junk = MemoryCache.getLoadedResource(u);
        }
        System.err.println("Done");
    }
}

                                    

Comments
EVALUATION

In this case there are multiple (actually hundreds of thousands) requests to 
obtain same object from the deployment cache. 

On every request we save WeakReference object to track usage of the returned object.
For this scenario this means hundreds of thousands of objects for 

We do not need to keep multiple references to track same objects. 
It is enough to update implementation of equals() to avoid this.
                                     
2009-01-05



Hardware and Software, Engineered to Work Together