Bug 63685 - WebappClassLoaderBase and StandardRoot reload classes from jar files on each call of getResource, getResourceAsStream and etc. that hit application startup performance
Summary: WebappClassLoaderBase and StandardRoot reload classes from jar files on each ...
Status: RESOLVED WONTFIX
Alias: None
Product: Tomcat 8
Classification: Unclassified
Component: Catalina (show other bugs)
Version: 8.5.x-trunk
Hardware: PC All
: P2 major (vote)
Target Milestone: ----
Assignee: Tomcat Developers Mailing List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2019-08-22 12:26 UTC by Alexandr
Modified: 2019-10-03 09:30 UTC (History)
0 users



Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Alexandr 2019-08-22 12:26:46 UTC
During profiling of the application I found interesting behavior related to class loading and caching in org.apache.catalina.loader.WebappClassLoaderBase and org.apache.catalina.webresources.StandardRoot.

Situation.
I have very big Spring application which consists of a bit more than 1500 jar files.
Startup of this application takes quite a lot of time. After investigation with logs analysis, profiling and debugging including Tomcat source code it was found out that one of the reasons is laid in Tomcat classloading behavior.
A lot of methods of Tomcat's org.apache.catalina.loader.WebappClassLoaderBase calls for org.apache.catalina.WebResourceRoot#getClassLoaderResource(String path), for example:
- public URL getResource(String name);
- public URL findResource(final String name);
- public InputStream getResourceAsStream(String name).

Every time as one of these methods is invoked for .class files it leads to invocation of org.apache.catalina.webresources.StandardRoot#getResourceInternal(String path, boolean useClassLoaderResources) method, which searches for the class file across all the jars in application WEB-INF\lib directory. And in my case it's about 1500 jar files.


In org.apache.catalina.webresources.StandardRoot already there is cache for WebResources. But currently it's used for static content only.
There is special check with comment in org.apache.catalina.webresources.Cache.class:

private boolean noCache(String path) {
        // Don't cache classes. The class loader handles this.
        // Don't cache JARs. The ResourceSet handles this.
        if ((path.endsWith(".class") &&
                (path.startsWith("/WEB-INF/classes/") || path.startsWith("/WEB-INF/lib/")))
                ||
                (path.startsWith("/WEB-INF/lib/") && path.endsWith(".jar"))) {
            return true;
        }
        return false;
    }
Comment tells that classes cache is handled by class loader, but in fact classloader caches only instances of the created classes, but not the content of the .class files or their URLs.


After modification of the noCache method as below:
private boolean noCache(String path) {
        // Don't cache JARs. The ResourceSet handles this.
        return (path.startsWith("/WEB-INF/lib/") && path.endsWith(".jar"));
    }
and setting cacheMaxSize and cacheTtl parameters, time of application startup was reduced in average about 5 minutes.
I understand that it's not an issue for a small application, but as we see from the result in case of a huge application it can give noticeable speedup of application launch.


In my case I faced with performance problem mainly in case of application startup but theoretically this behavior can bring negative impact on work of application in runtime as well. 

Would you be so kind to let me know why classes cache was intentionally skipped in org.apache.catalina.webresources.Cache.class and if it's possible to enable it and promote such fix?
Or if it's a bad idea what are the potential drawbacks I can face if I decide to enable cache of classes in Tomcat for my application?

Thank you!
Comment 1 Mark Thomas 2019-08-30 18:01:59 UTC
It is expected that a class file is read once and once only when the class is loaded into memory. Therefore there is no point caching the contents of a class file.

What use case requires that classes are loaded as resource files rather than classes? If there is sufficient justification we can always add an option to cache class files.
Comment 2 Mark Thomas 2019-09-10 20:01:16 UTC
Leaving this open for now but it will be closed as WONTFIX if no justification for caching classes is provided.
Comment 3 Andrei Ivanov 2019-09-11 08:35:42 UTC
I remember having a problem with AOP and load time weaving where Tomcat was scanning the WAR for servlet annotations and caching the classes so by the time the app was starting and LTW enabled, the classloader was returning the cached classes instead of passing them through the instrumentation agent,
Comment 4 Mark Thomas 2019-09-24 18:41:56 UTC
(In reply to Andrei Ivanov from comment #3)
> I remember having a problem with AOP and load time weaving where Tomcat was
> scanning the WAR for servlet annotations and caching the classes so by the
> time the app was starting and LTW enabled, the classloader was returning the
> cached classes instead of passing them through the instrumentation agent,

That is a known issue with the Servlet spec:
https://github.com/eclipse-ee4j/servlet-api/issues/79


This bug report is still waiting for a justification for caching the content of class files.
Comment 5 Mark Thomas 2019-10-03 09:30:04 UTC
This issue has been open for more than a month waiting for a use case that requires the caching of class files. None has been provided so I am closing this as WONTFIX.

Feel free to re-open this with an explanation of why this feature is required if you have such a use case