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

Reply via email to