JDK-6786318 : MemoryCache leaks memory on repetative requests for the same resource
  • Type: Bug
  • Component: deploy
  • Sub-Component: deployment_toolkit
  • Affected Version: 6,6u10
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: generic,windows_xp
  • CPU: generic,x86
  • Submitted: 2008-12-17
  • Updated: 2010-09-17
  • Resolved: 2009-01-26
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.
JDK 6 JDK 7
6u12 b04Fixed 7Fixed
Related Reports
Duplicate :  
Relates :  
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.
05-01-2009