Bug 58023 - Memory Leak in WebappClassLoader
Memory Leak in WebappClassLoader
Status: RESOLVED FIXED
Product: Tomcat 7
Classification: Unclassified
Component: Catalina
7.0.59
PC All
: P2 normal (vote)
: ---
Assigned To: Tomcat Developers Mailing List
:
Depends on:
Blocks:
  Show dependency tree
 
Reported: 2015-06-11 12:59 UTC by Matheus
Modified: 2015-06-16 19:25 UTC (History)
0 users



Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Matheus 2015-06-11 12:59:17 UTC
Memory leak occur because a reference of entry.manifest is never removed.

Problem:
1 - ${class}.class.getResource( ${resource.name1} );
2 - JarFile load a manifest.
3- resourceEntries put a new ResourceEntry with a manifest reference.
4 - ${class}.class.getResource( ${resource.name2} );
5 - resourceEntries put a new ResourceEntry with a manifest reference. Manifest contains ~20MB, but, no problem because the JarFile is the same reference. In other words, only one Manifest in the Heap space.
6 - wait for 90000 milliseconds.
7 - ${class}.class.getResource( ${resource.name3} );
8 - WebappClassLoader.closeJARs because time is elapsed 90000 milliseconds and load a new JarFile instances.
9 - JarFile load a new manifest.
10 - resourceEntries put a new ResourceEntry with a new manifest reference.
11 - wait for 90000 milliseconds
...
And this will be memory leak in little time (See resourceEntries.png).
In attachment, a scenario to simulate this problem.

PS.: A jar file need to be signed for accelerate leak, because of Manifest.entries ~5MB (see manifests.png) retained heap (in test case, but, my real app is ~20MB).
I attached the heap dump used in this test case (heap.zip).

Solution:
Always release the manifest reference.

Workaround:
public class WebappClassLoader
    extends 
        org.apache.catalina.loader.WebappClassLoader
{
    @Override
    public InputStream getResourceAsStream( String name )
    {
        InputStream in = super.getResourceAsStream( name ); 
        
        ResourceEntry entry = resourceEntries.get( name );
        
        if ( entry != null )
        {
            // prevent a memory leak
            entry.manifest = null;
            entry.certificates = null;
        }
        
        return in;
    }
    
    @Override
    public URL getResource( String name )
    {
        URL url = super.getResource( name );
        
        ResourceEntry entry = resourceEntries.get( name );
        
        if ( entry != null )
        {
            // prevent a memory leak
            entry.manifest = null;
            entry.certificates = null;
        }
        
        return url;
    }
}
Comment 1 Mark Thomas 2015-06-16 19:25:43 UTC
Thanks for the report. Strictly I would class this as excessive memory usage rather than a leak since a) there is an upper limit to the memory that will be used and b) the memory will be recovered once the application has been undeployed. Regardless of how it is classified, it is a bug.

I have applied an alternative fix (only request and retain the manifest while loading a class and then drop it) that should achieve the same ends for trunk (9.0.x), 8.0.x (for 8.0.24 onwards) and 7.0.x (for 7.0.63 onwards).