Introduction and Motivation
Classloading can be one of the nasty issues in a J2EE environment. Questions come to mind like: How to integrate with 3rd party libraries, which have conflicting classes, how to use remote stubs, etc.
This article will describe an especially sophisticated classloading system that combines ease of use with efficiency and compliant behaviour in order to deal with the mentioned questions. This classloading approach also contains a mechanism to separate internally used libraries from user-installable libraries that could potentially conflict with each other.
As an example, such advanced classloading mechanisms are implemented in IONA's Orbix E2A Application Server. It's classloading techniques will be illustrated and their usage demonstrated.
Developers of J2EE applications can be faced with various classloading problems. They include the following:
The J2EE specification is quite strict with regard to the visibility of classes or libraries across applications and even across modules within one application. Strictly following the J2EE spec may lead to duplication of the classes or libraries, which could be problematic both at development and run-time. Also, following the J2EE specification may lead to wasting runtime memory if identical libraries are loaded multiple times by different modules.
Sophisticated Application Server classloading addresses this by providing an automatic sharing mechanism that will share classloaders amongst identical libraries across modules and even applications. This avoids duplication of libraries in runtime memory, even though the J2EE compliant application (.ear file) may have these libraries duplicated on disk.
Alternatively, the application server also provides an optional single classloader mode, which give modules within an application visibility of each other's classes.
Another common problem J2EE developers may come across is the incompatibility of libraries used by the J2EE apps and other versions of the same libraries internally used by the application server.
As an example, take a J2EE app that requires a particular version of Xerces. The application server may already load another version of Xerces and provide this to the J2EE apps as part of the JAXP requirement. In the case the J2EE app does not want to use the JAXP parser provided by the application server, but rather use its own, this can be problematic. The standard Java classloading mechanism will normally serve the version of Xerces loaded by the application server, because the application server classloader exists higher in the classloading hierarchy.
An advanced classloading system would have a separation mechanism that allows one to hide libraries available in the application server from installable J2EE applications. This mechanism can be used to inhibit the JAXP functionality, if necessary, giving a J2EE application the possibility to provide its own.
The same mechanism also automatically hides non-J2EE libraries from user applications. As an example, take Log4J. The application server internally uses this library, which really is an implementation detail. The separation mechanism automatically hides Log4J from installable applications to allow them to provide their own Log4J if they need to.
Approach: Advanced Classloading
An approach to tackle the above mentioned classloading nastiness, is the introduction of an advanced classloading system, with a couple of different classloading mechanisms. The goal is to provide elements which combine ease of use with efficiency and compliant behaviour. Moreover, mechanisms are introduced, which allow to separate internally used libraries from user-installable libraries that could potentially conflict with each other.
In the following sections we will describe a variety of classloading configurations and modes. Each of them addresses particular classloading issues. For each of the introduced features we will summarize the problems it approaches and the solution provided.
General Classloading: FireWall ClassLoader
The first advanced classloading feature is the so called "Firewall Classloader". The idea behind a Firewall Classloader is - analogous to it's security related relatives - to separate different classloading domains from each other. With such functionality, the problem of different versions of public class libraries in the user application code and the application server itself, can easily be solved.
Solution: A FireWall ClassLoader can block requests for classes and resources from going up in the classloader hierarchy. It does not load any classes itself. The first FireWall ClassLoader is used to separate the JDK environment from the application server. The application server wants to control the classloading internally used, to ensure that it works correctly and cannot be disturbed by elements on the JVM's classpath, which were not expected in advance.
The application server classloader hierarchy with the FireWall ClassLoaders is the following:
The JDK FireWall ClassLoader provides this separation. It only allows requests for JDK J2SE classes and resources to reach the System Classloader. All other classes (e.g. J2EE ones) will be loaded by the Application Server ClassLoader (or user-level classloaders)1 .The Application Server ClassLoader is used to load all classes needed by the application server. This includes all J2EE classes, but also classes used internally by the Application Server.
Usage example: The Application Level FireWall ClassLoader is used to separate public libraries internally used by the application server from similar libraries possibly used in user-code (servlets, EJB's etc). As an example, take the Log4J logging library. The application server and many of the related tools could use a particular version of this library. However, it can happen that a user application also requires Log4J (possibly another version of it). Without the Application Level FireWall ClassLoader, the internal version of this library would always be loaded first by the JVM, hiding the version embedded in the installed application. The Application Level FireWall ClassLoader only allows through J2SE, J2EE and all classes and resources in (sub-packages of) com.iona.*. So, Log4J classes are blocked giving the User-Level ClassLoaders the opportunity to load their own version. This mechanism applies to any public jar possibly used by the application server that is not part of J2SE or J2EE.
Approach: The FireWall ClassLoaders work by comparing the package name of a requested class or resource to a list of filters that are allowed through to the parent classloader. The system also has a discovery mechanism that can take a jar and discover the package filters necessary to let through all the classes in this jar. This discovery system is automatically run over the runtime libraries of the currently installed JDK (e.g. rt.jar) to construct the JDK FireWall ClassLoader. It is also used to construct the Application-Level FireWall ClassLoader.
Application server administrators can modify the configuration of the FireWall ClassLoaders in the classloader hierarchy to either hide functionality or load extra classes from the system classpath.
For example, imagine an application that required its own XML parser. Here you would want to hide the XML parser used by the application server. To fulfill this need, the FireWall ClassLoader would hide the JAXP implementation inside the application server. When a deployed EJB or Servlet obtain an XML parser, the local version of it will be used.
The following sections describe the different classloading modes available to J2EE applications installed in the Application Server. The effect of these modes is made visible with the classloader configuration servlet.
Where the FireWall ClassLoader applies to the whole of the application server, the classloading modes can be applied to every J2EE application, when deployed. The following section describes these classloading modes.
Ease of use: Single Classloader mode
Problem: The single classloader mode was designed to make it easy for people to get started with the application server without being very strict around classloading requirements, manifest class-paths, client view jars etc. It can also be useful in cases where the application previously picked up libraries from the system classpath.
Example: An existing J2EE application was obtained from somewhere. The J2EE application was running before in a older generation application server. It is not packaged in a compliant way and there are many class references across modules. While it would be good to correctly package the application, the user may be more interested in actually getting it to run. Single classloader mode can help in this case.
Approach: In single classloader mode, all modules inside an .ear file will share the same classloader. When the application is deployed in this mode, the classloader-information servlet will show the following classloader hierarchy:
The bottom classloader (BytesProviderClassLoader@3f99af) in the tree is the single classloader for the whole application. It is the aggregation of all the loaders created for the application.
Per-module Classloading modes
In the per-module modes, every module will, in general, get it's own classloader(s). This means that they cannot load each other's classes any more without specifying a Class-Path attribute in the MANIFEST.MF file of a jar or placing a client-view jar in the lib directory of a war archive. For connectors, a classloader to a RAR archive will automatically be obtained through the RAR JNDI name.
Problem: One disadvantage of using a separate classloader for every module is that it may consume a lot of memory.
Example: If your application contains an EJB and a war archive and both of them need the same library, this means that for this single application, this library will be loaded in memory at least 2 times: once by the classloader of each module of the application.
Approach: To avoid this drawback, a memory-efficient classloading system could be used. The memory-efficient classloading tries to share library classloaders across modules and applications. Libraries are generally just jar files. To achieve sharing, libraries will get their own separate classloader. This classloader only loads the library, and nothing else. (If the library has a dependency on another library this can be specified in the Manifest Class-Path attribute as per the Jar specification).
All classloaders are given a key describing what they load. If they load a particular library, this key contains unique information about this library. Things such as its name (e.g. xerces.jar), size and CRC are stored.
If a new classloader is needed, the key to this classloader is constructed first and looked up in global classloader storage. If a classloader for this key is already present, it is reused and shared in this way. Applications use a delegation classloader to access this library classloader, so any number of such classloaders can be combined into the application's delegation classloader. Because the library classloader only loads a specific library and nothing else it is safe to share them in different applications. The library will only be shared if the applications are using the exact same library and its classloader will not be able to load anything but this library and direct dependencies. Memory efficient classloading can be very useful if care is taken in picking out the libraries for deployable applications. E.g. when the application server administrator specifies that all installable applications must use a specific instance of a certain library (e.g. batik.jar) if they need access to it, deploying multiple applications will be light on memory as batik.jar will only be loaded once in memory.
Memory-efficient per-module classloading exists in 2 flavours. Because the J2EE specification dictates that all jars in the lib directory of a war archive share the same classloader, standard per-module classloading adheres to this rule. This means that standard per-module classloading does not use the classloader sharing for jars in the lib directory in a war archive. There is also per-module classloading that applies memory-efficiency to the lib directory of war archives as well. The only extra requirement (in addition to J2EE requirements) this places on the jars in the lib directory is that if a jar in this location depends on another jar, this must be specified in the Manifest classpath, as per the jar specification.
For example, take an application modelling a Car repair shop. The application is packaged as an EAR file containing a WAR and EJB-JAR's. The application, in per-module Classloading mode produces the following classloader hierarchies:
At the top of Figure 3, you can find the classloading hierarchy of the war archive, on the bottom side the classloading configuration of the EJB. The BytesProviderDelegationClassLoader@1daa17 is the main classloader for the servlet. It loads the contents of WEB-INF/classes. In addition it delegates to four other classloaders.
- One that loads the root of the war archive (for static content)
- One for classloader_util.jar, a utility jar used both by the servlet and the EJB
- One for JDom and Xerces 2.0.0. This library loader and the previous one are constructed from the contents of the WEB-INF/lib directory.
- Finally a classloader that loads the client-view jar of the EJB. This loader is added by the builder tool when it found out that the servlet was accessing the EJB.
On the right side, the main classloader for the EJB is the BytesProviderDelegationClassLoader b5
This classloader loads everything in the carshop.jar, where the EJB lives. This classloader also delegates to other classloaders:
- One classloader that loads JDom and Xerces 2.0.0
- One that loads classloader_util.jar. Both these are triggered by the Manifest Class-Path in the carshop.jar.
If you look at the instance numbers of the classloaders, you can see that the loaders for JDom/Xerces are shared across the Servlet & EJB. The same is true for the classloader_util.jar. This happens even though the actual jars don't live in the same location, as for the war they are stored in the WEB-INF/lib but for the EJB they live in the root of the .ear file. Sharing can happen regardless of location. It is the key that determines whether or not a jar can be shared. The key does not contain the original location of the jar.
Simple per-module classloading
The Application Server also supports a simple per-module classloading mode. In this mode every module just gets a single classloader loading everything for this module. Classloader sharing will most likely not happen with this mode.
Classloader grouping for framework jars
You may have wondered why the memory-efficient classloading used one classloader for JDom/Xerces which loaded three jars instead of just one library jar.This is necessary because JDom is a framework jar. It requires an XML parser, but does not know which XML parser it will use in advance. Therefore it cannot declare its dependency using a Manifest Class-Path entry.
How did we achieve creating one classloader to load JDom and it's XML parser? This is done by specifying jar-dependencies. Jar-dependencies are like Manifest class-path entries, but they can be specified in the application server configuration.
The car application mentioned earlier would include the following configuration:
This means that whenever the jar jdom.jar is encountered in an archive, the classloading system tries to create a classloader that loads xmlParserAPIs.jar and xercesImpl.jar too. These jars are looked up from the same location where jdom.jar is found (just like with real Jar file Manifests).
Not being able to resolve all dependencies is not considered a fatal error, so it is possible to write a line that takes either xerces 1.x or 2.x, whichever is available:
<jar-dependencies> "jdom.jar=xmlParserAPIs.jar,xercesImpl.jar,xerces.jar" </jar-dependencies>
Classloading can be one of the nasty beasts in a J2EE application server, because J2EE application servers and J2EE applications can be very complex, having potentially conflicting requirements of their environment.
In this article we described solutions, which allow you to overcome:
- the possible interference between application server libraries and application libraries
- memory consumption problems that can occur when identical libraries are used in more than one J2EE module.
- porting problems that can occur when porting an older or incorrectly packaged J2EE application.
The advanced classloading techniques to achieve this were illustrated. They are implemented in IONA's Orbix E2A Application Server Platform.
- Java Language Specification, ClassLoading section:
- Application Assembly in the J2EE specification: