I analyzed a fun problem a few weeks ago. Someone was using the shared
loader extensively (TC 5.5). They observed performance problems and
thread dumps showed, that often the class loader locking was the culprit.
Code was inside loadClass(). Now it turned out, it wasn't about really
loading classes. Instead there was a lot of deserialization going on
(data received from a backend app server). Deserialization uses
reflection intensively and reflection leads to loadClass() calls to
retrieve the classes. We observed e.g. several hundreds loadClass()
calls per second.
Now loadClass() in the WebappClasLoader does:
- check own class cache
- check super class cache
- try loading from system loader
- call Class.forName with parent loader (which calls loadClass() there)
[only if "delegated", which is *not* the default]
- try loading via findClass()
- call Class.forName with parent loader (which calls loadClass() there)
[only if not "delegated", which *is* the default]
So if a class was previously loaded by the shared loader (or common or
server), then we will not find in in our own or the super cache, will
then *always* try to load it via system, will then (if default) always
try to load it ourselves and only finally will try to load it via parent
and find it there in the cache.
This turned out to become a bottleneck.
I implemented a quick hack which cached the classes loaded by system,
parent and shared positively and negatively (not found) in the
WebappLoader using the same method that was already used for its own
cache. Thus the massive calls to those loaders could be avoided and the
bottleneck went away.
I wonder whether we want to improve caching in the WebappLoader. Of
course most deployments no longer use shared or common to share many
application classes, but it is still a supported feature and for some
classes like JDBC it is standard.
What we could do to keep the design simple is caching any positive
result from loadClass() in the WebappLoader, even it it was found via
super, system or parent. In addition we could also cache negative
results for all those. The biggest downsides I can see would be
- less dynamics: if someone had a more dynamic loader unerneath ours,
which would change the result of loadClass() during runtime, we would
shield the app from it, because we now return classes from our cache.
- increased memory use for the cache, i.e. the list of class names and
references to the classes.
To stay completely compatible I think the feature should not be default,
at least until TC 7, maybe switch default for 8.
What do you think? Does it make sense? Should I prepare a patch for trunk?
Regards,
Rainer
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org