Let's now look at the JBoss JMX implementation. The JBoss ClassLoader ArchitectureJBoss employs a class-loading architecture that facilitates sharing of classes across deployment units and hot deployment of services and applications. Before we discuss the JBoss-specific class-loading model, however, you need to understand the nature of Java's type system and how class loaders fit in. Class Loading and Types in JavaClass loading is a fundamental part of all server architectures. Arbitrary services and their supporting classes must be loaded into the server framework. This can be problematic due to the strongly typed nature of Java. Most developers know that the type of a class in Java is a function of the fully qualified name of the class. However, the type is also a function of the java.lang.ClassLoader that is used to define that class. This additional qualification of type is necessary to ensure that environments in which classes may be loaded from arbitrary locations may be type safe. However, in a dynamic environment such as an application server, and especially JBoss, with its support for hot deployment, class cast exceptions, linkage errors, and illegal access errors can show up in ways not seen in more static class-loading contexts. Let's take a look at the meaning of each of these exceptions and how they can happen. ClassCastException: I'm Not Your TypeA java.lang.ClassCastException results whenever an attempt is made to cast an instance to an incompatible type. A simple example is trying to obtain a String from a List into which a URL was placed: ArrayList array = new ArrayList(); array.add(new URL("file:/tmp")); String url = (String) array.get(0); java.lang.ClassCastException: java.net.URL at org.jboss.chap2.ex0.ExCCEa.main(Ex1CCE.java:16) The ClassCastException tells you that the attempt to cast the array element to a String failed because the actual type was URL. This trivial case is not what we are interested in, however. Consider the case of a JAR being loaded by different class loaders. Although the classes loaded through each class loader are identical in terms of the bytecode, they are completely different types, as viewed by the Java type system. Listing 2.1 illustrates an example of this. Listing 2.2 shows the additional classes that are used. Listing 2.1. The ExCCEc Class, Used to Demonstrate ClassCastException Due to Duplicate Class Loaderspackage org.jboss.chap2.ex0; import java.io.File; import java.net.URL; import java.net.URLClassLoader; import java.lang.reflect.Method; import org.apache.log4j.Logger; import org.jboss.util.ChapterExRepository; import org.jboss.util.Debug; /** * An example of a ClassCastException that * results from classes loaded through * different class loaders. * @author Scott.Stark@jboss.org * @version $Revision: 1.5 $ */ public class ExCCEc { public static void main(String[] args) throws Exception { ChapterExRepository.init(ExCCEc.class); String chapDir = System.getProperty("chapter.dir"); Logger ucl0Log = Logger.getLogger("UCL0"); File jar0 = new File(chapDir+"/j0.jar"); ucl0Log.info("jar0 path: "+jar0.toString()); URL[] cp0 = {jar0.toURL()}; URLClassLoader ucl0 = new URLClassLoader(cp0); Thread.currentThread().setContextClassLoader(ucl0); Class objClass = ucl0.loadClass("org.jboss.chap2.ex0.ExObj"); StringBuffer buffer = new StringBuffer("ExObj Info"); Debug.displayClassInfo(objClass, buffer, false); ucl0Log.info(buffer.toString()); Object value = objClass.newInstance(); File jar1 = new File(chapDir+"/j0.jar"); Logger ucl1Log = Logger.getLogger("UCL1"); ucl1Log.info("jar1 path: "+jar1.toString()); URL[] cp1 = {jar1.toURL()}; URLClassLoader ucl1 = new URLClassLoader(cp1); Thread.currentThread().setContextClassLoader(ucl1); Class ctxClass2 = ucl1.loadClass("org.jboss.chap2.ex0.ExCtx"); buffer.setLength(0); buffer.append("ExCtx Info"); Debug.displayClassInfo(ctxClass2, buffer, false); ucl1Log.info(buffer.toString()); Object ctx2 = ctxClass2.newInstance(); try { Class[] types = {Object.class}; Method useValue = ctxClass2.getMethod("useValue", types); Object[] margs = {value}; useValue.invoke(ctx2, margs); } catch(Exception e) { ucl1Log.error("Failed to invoke ExCtx.useValue", e); throw e; } } } Listing 2.2. The ExCtx, ExObj, and ExObj2 Classes Used by the Examplesport java.io.IOException; import org.apache.log4j.Logger; import org.jboss.util.Debug; /** * Classes used to demonstrate various class * loading issues * @author Scott.Stark@jboss.org * @version $Revision: 1.5 $ */ public class ExCtx { ExObj value; public ExCtx() throws IOException { value = new ExObj(); Logger log = Logger.getLogger(ExCtx.class); StringBuffer buffer = new StringBuffer("ctor.ExObj"); Debug.displayClassInfo(value.getClass(), buffer, false); log.info(buffer.toString()); ExObj2 obj2 = value.ivar; buffer.setLength(0); buffer = new StringBuffer("ctor.ExObj.ivar"); Debug.displayClassInfo(obj2.getClass(), buffer, false); log.info(buffer.toString()); } public Object getValue() { return value; } public void useValue(Object obj) throws Exception { Logger log = Logger.getLogger(ExCtx.class); StringBuffer buffer = new StringBuffer("useValue2.arg class"); Debug.displayClassInfo(obj.getClass(), buffer, false); log.info(buffer.toString()); buffer.setLength(0); buffer.append("useValue2.ExObj class"); Debug.displayClassInfo(ExObj.class, buffer, false); log.info(buffer.toString()); ExObj ex = (ExObj) obj; } void pkgUseValue(Object obj) throws Exception { Logger log = Logger.getLogger(ExCtx.class); log.info("In pkgUseValue"); } } package org.jboss.chap2.ex0; import java.io.Serializable; /** * @author Scott.Stark@jboss.org * @version $Revision: 1.5 $ */ public class ExObj implements Serializable { public ExObj2 ivar = new ExObj2(); } package org.jboss.chap2.ex0; import java.io.Serializable; /** * @author Scott.Stark@jboss.org * @version $Revision: 1.5 $ */ public class ExObj2 implements Serializable { } The ExCCEc.main method uses reflection to isolate the classes that are being loaded by the class loaders ucl0 and ucl1 from the application class loader. Both are set up to load classes from the output/chap2/j0.jar, the contents of which are as follows: [examples]$ jar -tf output/chap2/j0.jar org/jboss/chap2/ex0/ExCtx.class org/jboss/chap2/ex0/ExObj.class org/jboss/chap2/ex0/ExObj2.class Let's run an example that demonstrates how a class cast exception can occur and then look at the specific issue with the example. See Appendix B, "Example Installation," for instructions on installing the examples accompanying the book, and then run the example from within the examples directory by using the following command:
Only the exception is shown here. The full output can be found in the logs/chap2-ex0c.log file. At line 55 of Ex-CCEc.java you invoke ExcCCECtx.useValue(Object) on the instance loaded and created in lines 3748, using ucl1. The ExObj that is passed in is the one loaded and created in lines 2535 via ucl0. The exception results when the ExCtx.useValue code attempts to cast the argument passed in to an ExObj. To understand why this fails, consider the debugging output from the chap2-ex0c.log file shown in Listing 2.3. Listing 2.3. The chap2-ex0c.log Debugging Output for the ExObj Classes[INFO,UCL0] ExObj Info org.jboss.chap2.ex0.ExObj(113fe2).ClassLoader=java.net.URLClassLoader@6e3914 ..java.net.URLClassLoader@6e3914 ....file:/C:/Scott/JBoss/Books/AdminDevel/education/books/admin-devel/examples/output/ chap2/j0.jar ++++CodeSource: (file:/C:/Scott/JBoss/Books/AdminDevel/education/books/admin-devel/examples/output/ chap2/j0.jar <no certificates>) Implemented Interfaces: ++interface java.io.Serializable(7934ad) ++++ClassLoader: null ++++Null CodeSource [INFO,ExCtx] useValue2.ExObj class org.jboss.chap2.ex0.ExObj(415de6).ClassLoader=java.net.URLClassLoader@30e280 ..java.net.URLClassLoader@30e280 ....file:/C:/Scott/JBoss/Books/AdminDevel/education/books/admin-devel/examples/output/ chap2/j0.jar ++++CodeSource: (file:/C:/Scott/JBoss/Books/AdminDevel/education/books/admin-devel/examples/output/ chap2/j0.jar <no certificates>) Implemented Interfaces: ++interface java.io.Serializable(7934ad) ++++ClassLoader: null ++++Null CodeSource The first output prefixed with [INFO,UCL0] shows that the ExObj class loaded at line ExCCEc.java:31 has a hash code of 113fe2 and an associated URLClassLoader instance with a hash code of 6e3914, which corresponds to ucl0. This is the class used to create the instance passed to the ExCtx.useValue method. The second output prefixed with [INFO,ExCtx] shows that the ExObj class, as seen in the context of the ExCtx.useValue method, has a hash code of 415de6 and a URLClassLoader instance with an associated hash code of 30e280, which corresponds to ucl1. So even though the ExObj classes are the same in terms of actual bytecode, since it comes from the same j0.jar, the classes are different, as shown by both the ExObj class hash codes and the associated URLClassLoader instances. Hence, attempting to cast an instance of ExObj from one scope to the other results in the ClassCastException. This type of error is common when you're redeploying an application to which other applications are holding references to classes from the redeployed application (for example, a standalone WAR accessing an EJB). If you are redeploying an application, all dependent applications must flush their class references. Typically, this requires that the dependent applications themselves be redeployed. An alternate means of allowing independent deployments to interact in the presence of redeployment would be to isolate the deployments by configuring the EJB layer to use the standard call-by-value semantics rather than the call-by-reference that JBoss defaults to for components collocated in the same VM. An example of how to enable call-by-value semantics is presented in Chapter 5, "EJBs on JBoss." IllegalAccessException: Doing What You Should NotA java.lang.IllegalAccessException is thrown when you attempt to access a method or member that visibility qualifiers do not allow. A typical example is attempting to access private or protected methods or instance variables. Another common example is accessing package-protected methods or members from a class that appears to be in the correct package but is really not, due to caller and callee classes being loaded by different class loaders. An example of this is illustrated by the code shown in Listing 2.4. Listing 2.4. The ExIAEd Class, Used to Demonstrate IllegalAccessException Due to Duplicate Class Loaderspackage org.jboss.chap2.ex0; import java.io.File; import java.net.URL; import java.net.URLClassLoader; import java.lang.reflect.Method; import org.apache.log4j.Logger; import org.jboss.util.ChapterExRepository; import org.jboss.util.Debug; /** * An example of IllegalAccessExceptions due to * classes loaded by two class loaders. * @author Scott.Stark@jboss.org * @version $Revision: 1.5 $ */ public class ExIAEd { public static void main(String[] args) throws Exception { ChapterExRepository.init(ExIAEd.class); String chapDir = System.getProperty("chapter.dir"); Logger ucl0Log = Logger.getLogger("UCL0"); File jar0 = new File(chapDir+"/j0.jar"); ucl0Log.info("jar0 path: "+jar0.toString()); URL[] cp0 = {jar0.toURL()}; URLClassLoader ucl0 = new URLClassLoader(cp0); Thread.currentThread().setContextClassLoader(ucl0); StringBuffer buffer = new StringBuffer("ExIAEd Info"); Debug.displayClassInfo(ExIAEd.class, buffer, false); ucl0Log.info(buffer.toString()); Class ctxClass1 = ucl0.loadClass("org.jboss.chap2.ex0.ExCtx"); buffer.setLength(0); buffer.append("ExCtx Info"); Debug.displayClassInfo(ctxClass1, buffer, false); ucl0Log.info(buffer.toString()); Object ctx0 = ctxClass1.newInstance(); try { Class[] types = {Object.class}; Method useValue = ctxClass1.getDeclaredMethod("pkgUseValue", types); Object[] margs = {null}; useValue.invoke(ctx0, margs); } catch(Exception e) { ucl0Log.error("Failed to invoke ExCtx.pkgUseValue", e); } } } The ExIAEd.main method uses reflection to load the ExCtx class via the ucl0 class loader, whereas the ExIEAd class is loaded by the application class loader. We will run this example to demonstrate how the IllegalAccessException can occur and then look at the specific issue with the example. You run the example by using the following command: [examples]$ ant -Dchap=chap2 -Dex=0d run-example Buildfile: build.xml ... [java] [ERROR,UCL0] Failed to invoke ExCtx.pkgUseValue [java] java.lang.IllegalAccessException: Class org.jboss.chap2.ex0.ExIAEd cannot access a member of class org.jboss.chap2.ex0.ExCtx with modifiers "" [java] at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:57) [java] at java.lang.reflect.Method.invoke(Method.java:317) [java] at org.jboss.chap2.ex0.ExIAEd.main(ExIAEd.java:48) The truncated output shown here illustrates the IllegalAccessException. The full output can be found in the logs/chap2-ex0d.log file. At line 48 of ExIAEd.java, the ExCtx.pkgUseValue(Object) method is invoked via reflection. The pkgUseValue method has package-protected access, and even though both the invoking class ExIAEd and the ExCtx class whose method is being invoked reside in the org.jboss.chap2.ex0 package, the invocation is seen to be invalid due to the fact that the two classes are loaded by different class loaders. You can see this by looking at the debugging output from the chap2-ex0d.log file: [INFO,UCL0] ExIAEd Info org.jboss.chap2.ex0.ExIAEd(65855a).ClassLoader=sun.misc.Launcher$AppClassLoader@3f52a5 ..sun.misc.Launcher$AppClassLoader@3f52a5 ... [INFO,UCL0] ExCtx Info org.jboss.chap2.ex0.ExCtx(70eed6).ClassLoader=java.net.URLClassLoader@113fe2 ..java.net.URLClassLoader@113fe2 ... The ExIAEd class was loaded via the default application class loader instance sun.misc.Launcher$AppClassLoader@3f52a5, whereas the ExCtx class was loaded by the java.net.URLClassLoader@113fe2 instance. Because the classes are loaded by different class loaders, access to the package-protected method is seen to be a security violation. So, not only is the type a function of both the fully qualified classname and class loader, the package scope is as well. An example of how this can happen in practice is including the same classes in two different SAR deployments. If classes in the deployments have a package-protected relationship, users of the SAR service may end up loading one class from SAR class that is loading at one point and then load another class from the second SAR at a later time. If the two classes in question have a protected access relationship, an IllegalAccessError will result. The solution is to either include the classes in a separate JAR that is referenced by the SARs or to combine the SARs into a single deployment. This can either be a single SAR or an EAR that includes both SARs. LinkageErrors: Making Sure You Are Who You Say You AreLoading constraints validate type expectations in the context of class loader scopes to ensure that a class X is consistently the same class when multiple class loaders are involved. This is important because Java allows for user-defined class loaders. Linkage errors are essentially an extension of the class cast exception that is enforced by the VM when classes are loaded and used. To understand what loading constraints are and how they ensure type safety, we will first look at the nomenclature of the Liang and Bracha paper "Dynamic Class Loading in the Java Virtual Machine," along with an example from that paper. There are two types of class loaders: initiating and defining. An initiating class loader is one that a ClassLoader.loadClass method has been invoked on to initiate the loading of the named class. A defining class loader is the loader that calls one of the ClassLoader.defineClass methods to convert the class bytecode into a Class instance. The most complete expression of a class is given by <C, Ld>Li, where C is the fully qualified classname, Ld is the defining class loader, and Li is the initiating class loader. In a context where the initiating class loader is not important, the type may be represented by <C, Ld>, and when the defining class loader is not important, the type may be represented by CLi. In the latter case, there is still a defining class loader; it's just not important what the identity of the defining class loader is. Also, a type is completely defined by <C, Ld>. The only time the initiating loader is relevant is when a loading constraint is being validated. Now consider the classes shown in Listing 2.5. Listing 2.5. Classes Demonstrating the Need for Loading Constraintsclass <C,L1> { void f() { <Spoofed, L1>L1x = <Delegated, L2>L2 x.secret_value = 1; // Should not be allowed } } class <Delegated,L2> { static <Spoofed, L2>L3 g() {...} } } class <Spoofed, L1> { public int secret_value; } class <Spoofed, L2> { private int secret_value; } The class C is defined by L1, and so L1 is used to initiate loading of the classes Spoofed and Delegated that are referenced in the C.f() method. The Spoofed class is defined by L1, but Delegated is defined by L2 because L1 delegates to L2. Because Delegated is defined by L2, L2 will be used to initiate loading of Spoofed in the context of the Delegated.g() method. In this example, L1 and L2 define different versions of Spoofed, as indicated by the two versions shown at the end of Listing 2.5. Because C.f() believes x is an instance of <Spoofed, L1>, it is able to access the private field secret_value of <Spoofed, L27> that is returned by Delegated.g() due to the 1.1 and earlier Java VM's failure to take into account that a class type is determined by both the fully qualified name of the class and the defining class loader. Java addresses this problem by generating loader constraints to validate type consistency when the types being used are coming from different defining class loaders. For the Listing 2.5 example, the VM generates a constraint SpoofedL1=SpoofedL2 when the first line of method C.f() is verified to indicate that the type Spoofed must be the same, regardless of whether the load of Spoofed is initiated by L1 or L2. It does not matter whether L1 or L2or even some other class loaderdefines Spoofed. All that matters is that there is only one Spoofed class defined, regardless of whether L1 or L2 was used to initiate the loading. If L1 or L2 has already defined separate versions of Spoofed when this check is made, a LinkageError will be generated immediately. Otherwise, the constraint will be recorded, and when Delegated.g() is executed, any attempt to load a duplicate version of Spoofed will result in a LinkageError. Now let's take a look at how a LinkageError can occur by examining a concrete example. Listing 2.6 gives the example's main class, along with the custom class loader used. Listing 2.6. A Concrete Example of a LinkageErrorpackage org.jboss.chap2.ex0; import java.io.File; import java.net.URL; import org.apache.log4j.Logger; import org.jboss.util.ChapterExRepository; import org.jboss.util.Debug; /** * An example of a LinkageError due to classes being defined by more * than one class loader in a non-standard class loading environment. * * @author Scott.Stark@jboss.org * @version $Revision: 1.5 $ */ public class ExLE { public static void main(String[] args) throws Exception { ChapterExRepository.init(ExLE.class); String chapDir = System.getProperty("chapter.dir"); Logger ucl0Log = Logger.getLogger("UCL0"); File jar0 = new File(chapDir+"/j0.jar"); ucl0Log.info("jar0 path: "+jar0.toString()); URL[] cp0 = {jar0.toURL()}; Ex0URLClassLoader ucl0 = new Ex0URLClassLoader(cp0); Thread.currentThread().setContextClassLoader(ucl0); Class ctxClass1 = ucl0.loadClass("org.jboss.chap2.ex0.ExCtx"); Class obj2Class1 = ucl0.loadClass("org.jboss.chap2.ex0.ExObj2"); StringBuffer buffer = new StringBuffer("ExCtx Info"); Debug.displayClassInfo(ctxClass1, buffer, false); ucl0Log.info(buffer.toString()); buffer.setLength(0); buffer.append("ExObj2 Info, UCL0"); Debug.displayClassInfo(obj2Class1, buffer, false); ucl0Log.info(buffer.toString()); File jar1 = new File(chapDir+"/j1.jar"); Logger ucl1Log = Logger.getLogger("UCL1"); ucl1Log.info("jar1 path: "+jar1.toString()); URL[] cp1 = {jar1.toURL()}; Ex0URLClassLoader ucl1 = new Ex0URLClassLoader(cp1); Class obj2Class2 = ucl1.loadClass("org.jboss.chap2.ex0.ExObj2"); buffer.setLength(0); buffer.append("ExObj2 Info, UCL1"); Debug.displayClassInfo(obj2Class2, buffer, false); ucl1Log.info(buffer.toString()); ucl0.setDelegate(ucl1); try { ucl0Log.info("Try ExCtx.newInstance()"); Object ctx0 = ctxClass1.newInstance(); ucl0Log.info("ExCtx.ctor succeeded, ctx0: "+ctx0); } catch(Throwable e) { ucl0Log.error("ExCtx.ctor failed", e); } } } package org.jboss.chap2.ex0; import java.net.URLClassLoader; import java.net.URL; import org.apache.log4j.Logger; /** * A custom class loader that overrides the standard parent delegation * model * * @author Scott.Stark@jboss.org * @version $Revision: 1.5 $ */ public class Ex0URLClassLoader extends URLClassLoader { private static Logger log = Logger.getLogger(Ex0URLClassLoader.class); private Ex0URLClassLoader delegate; public Ex0URLClassLoader(URL[] urls) { super(urls); } void setDelegate(Ex0URLClassLoader delegate) { this.delegate = delegate; } protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { Class clazz = null; if (delegate != null) { log.debug(Integer.toHexString(hashCode()) + "; Asking delegate to loadClass: " + name); clazz = delegate.loadClass(name, resolve); log.debug(Integer.toHexString(hashCode()) + "; Delegate returned: "+clazz); } else { log.debug(Integer.toHexString(hashCode()) + "; Asking super to loadClass: "+name); clazz = super.loadClass(name, resolve); log.debug(Integer.toHexString(hashCode()) + "; Super returned: "+clazz); } return clazz; } protected Class findClass(String name) throws ClassNotFoundException { Class clazz = null; log.debug(Integer.toHexString(hashCode()) + "; Asking super to findClass: "+name); clazz = super.findClass(name); log.debug(Integer.toHexString(hashCode()) + "; Super returned: "+clazz); return clazz; } } The key component in this example is the URLClassLoader subclass Ex0URLClassLoader. This class loader implementation overrides the default parent delegation model to allow the ucl0 and ucl1 instances to both load the Ex-Obj2 class and then set up a delegation relationship between ucl0 to ucl1. At lines 30 and 31, the ucl0 Ex0URLClassLoader is used to load the ExCtx and ExObj2 classes. At line 45 of ExLE.main, the ucl1 Ex0URLClassLoader is used to load the ExObj 2 class again. At this point, both the ucl0 and ucl1 class loaders have defined the ExObj2 class. A delegation relationship from ucl0 to ucl1 is then set up at line 51 via the ucl0.setDelegate(ucl1) method call. Finally, at line 54 of ExLE.main, an instance of ExCtx is created, using the class loaded via ucl0. The ExCtx class is the same as that presented in Listing 2.2, and the constructor is as follows: public ExCtx() throws IOException { value = new ExObj(); Logger log = Logger.getLogger(ExCtx.class); StringBuffer buffer = new StringBuffer("ctor.ExObj"); Debug.displayClassInfo(value.getClass(), buffer, false); log.info(buffer.toString()); ExObj2 obj2 = value.ivar; buffer.setLength(0); buffer = new StringBuffer("ctor.ExObj.ivar"); Debug.displayClassInfo(obj2.getClass(), buffer, false); log.info(buffer.toString()); } Now, because the ExCtx class was defined by the ucl0 class loader, and at the time the ExCtx constructor is executed, ucl0 delegates to ucl1, line 24 of the ExCtx constructor involves the following expression, which has been rewritten in terms of the complete type expressions: <ExObj2,ucl0>ucl0 obj2 = <ExObj,ucl1>ucl0 value * ivar This generates a loading constraint of ExObj 2ucl0 = ExObj 2ucl1 because the ExObj 2 type must be consistent across the ucl0 and ucl1 class loader instances. Because you have loaded ExObj2 using both ucl0 and ucl1 prior to setting up the delegation relationship, the constraint will be violated and should generate a LinkageError when run. Run the example by using the following command:
As expected, a LinkageError is thrown during the validation of the loader constraints required by line 24 of the ExCtx constructor. Debugging Class-Loading IssuesDebugging class-loading issues comes down to finding out where a class was loaded from. A useful tool for this is the code snippet shown in Listing 2.7, taken from the org.jboss.util.Debug class in the book examples. Listing 2.7. Obtaining Debugging Information for a ClassClass clazz =...; StringBuffer results = new StringBuffer(); ClassLoader cl = clazz.getClassLoader(); results.append("\n" + clazz.getName() + "(" + Integer.toHexString(clazz.hashCode()) + ").ClassLoader=" + cl); ClassLoader parent = cl; while (parent != null) { results.append("\n.."+parent); URL[] urls = getClassLoaderURLs(parent); int length = urls != null ? urls.length : 0; for(int u = 0; u < length; u ++) { results.append("\n...."+urls[u]); } if (showParentClassLoaders == false) { break; } if (parent != null) { parent = parent.getParent(); } } CodeSource clazzCS = clazz.getProtectionDomain().getCodeSource(); if (clazzCS != null) { results.append("\n++++CodeSource: "+clazzCS); } else { results.append("\n++++Null CodeSource"); } Every Class object knows its defining ClassLoader, and this is available via the getClassLoader() method. This defines the scope in which the Class type is known, as shown in the previous sections on class cast exceptions, illegal access exceptions, and linkage errors. From the class loader you can view the hierarchy of class loaders that make up the parent delegation chain. If the class loader is a URLClassLoader, you can also see the URLs used for class and resource loading. The defining class loader of a Class cannot tell you from what location that Class object was loaded. To determine that, you must obtain the java.security.ProtectionDomain and then the java.security.CodeSource. It is the CodeSource that has the URL location from which the class originated. Note that not every Class object has a CodeSource. If a class is loaded by the bootstrap class loader, then its CodeSource will be null. This is the case for all classes in the java.* and javax.* packages, for example. Beyond that, it may be useful to view the details of classes being loaded into the JBoss server. You can enable verbose logging of the JBoss class-loading layer by using a log4j configuration fragment like that shown in Listing 2.8. Listing 2.8. An Example of a log4j.xml Configuration Fragment for Enabling Verbose Class-Loading Logging<appender name="UCL" > <param name="File" value="${jboss.server.home.dir}/log/ucl.log"/> <param name="Append" value="false"/> <layout > <param name="ConversionPattern" value="[%r,%c{1},%t] %m%n"/> </layout> </appender> <category name="org.jboss.mx.loading" additivity="false"> <priority value="TRACE" /> <appender-ref ref="UCL"/> </category> This places the output from the classes in the org.jboss.mx.loading package into the ucl.log file of the server configuration's log directory. Although it may not be meaningful if you have not looked at the class-loading code, it is vital information that is needed for submitting bug reports or questions regarding class-loading problems. If you have a class-loading problem that appears to be a bug, you should submit it to the JBoss project on SourceForge and include this log file as an attachment. Inside the JBoss Class-Loading ArchitectureNow that you have the role of class loaders in the Java type system defined, let's take a look at the JBoss class-loading architecture, which is shown in Figure 2.3. Figure 2.3. The core JBoss class-loading components.
The central component of the JBoss class-loading architecture is the org.jboss.mx.loading.UnifiedClassLoader3 (UCL) class loader. This is an extension of the standard java.net.URLClassLoader that overrides the standard parent delegation model to use a shared repository of classes and resources. This shared repository is org.jboss.mx.loading.UnifiedLoaderRepository3. Every UCL is associated with a single UnifiedLoaderRepository3, and a UnifiedLoaderRepository3 typically has many UCLs. A UCL may have multiple URLs associated with it for class and resource loading. Deployers use the top-level deployment's UCL as a shared class loader, and all deployment archives are assigned to this class loader. We will talk about the JBoss deployers and their interaction with the class-loading system in more detail later in this chapter, in the section "JBoss MBean Services." When a UCL is asked to load a class, it first looks to the repository cache it is associated with to see if the class has already been loaded. The UCL loads it into the repository only if the class does not exist in the repository. By default, a single UnifiedLoaderRepository3 is shared across all UCL instances. This means the UCLs form a single flat class loader namespace. The following is the complete sequence of steps that occur when a UnfiedClassLoader3.loadClass(String,boolean) method is called:
Viewing Classes in the Loader RepositoryAnother useful source of information on classes is the UnifiedLoaderRepository itself. This is an MBean that contains operations to display class and package information. The default repository is located under the standard JMX name JMImplementation:name=Default,service=LoaderRepository, and you can access its MBean via the JMX Console by following its link from the front page. Figure 2.4 shows the JMX Console view of this MBean. Figure 2.4. The default class LoaderRepository MBean view in the JMX Console.Two useful operations you will find here are getPackageClassLoadersString) and displayClassInfo(String). The getPackageClassLoaders operation returns a set of class loaders that have been indexed to contain classes or resources for the given package name. The package name must have a trailing period. If you type in the package name org.jboss.ejb., the following information is displayed: [org.jboss.mx.loading.UnifiedClassLoader3@e26ae7{ url=file:/private/tmp/jboss-4.0.1/server/default/tmp/deploy/tmp11895jboss-service.xml, addedOrder=2}] This is the string representation of the set. It shows one UnifiedClassLoader3 instance with a primary URL pointing to the jboss-service.xml descriptor. This is the second class loader added to the repository (shown by addedOrder=2). It is the class loader that owns all the JARs in the lib directory of the server configuration (for example, server/default/lib). To view the information for a given class, you use the displayClassInfo operation, passing in the fully qualified name of the class to view. For example, if you use org.jboss.jmx.adaptor.html.HtmlAdaptorServlet, which is from the package we just looked at, the description is displayed. The information is a dump of the information for the Class instance in the loader repository if one has been loaded, followed by the class loaders that are seen to have the class file available. If a class is seen to have more than one class loader associated with it, then there is the potential for class-loading-related errors. Scoping ClassesIf you need to deploy multiple versions of an application, you need to use deployment-based scoping. With deployment-based scoping, each deployment creates its own class loader repository, in the form of a HeirarchicalLoaderRepository3 that looks first to the UnifiedClassLoader3 instances of the deployment units included in the EAR before delegating to the default UnifiedLoaderRepository3. To enable an EAR-specific loader repository, you need to create a META-INF/jboss-app.xml descriptor, as shown in Listing 2.9. Listing 2.9. An Example of a jboss-app.xml Descriptor for Enabled Scoped Class Loading at the EAR Level<jboss-app> <loader-repository>some.dot.com:loader=webtest.ear</loader-repository> </jboss-app> The value of the loader-repository element is the JMX object name to assign to the repository created for the EAR. This must be a unique and valid JMX ObjectName, but the actual name is not important. The Complete Class-Loading ModelThe previous discussion of the core class-loading components introduces the custom UnifiedClassLoader3 and UnifiedLoaderRepository3 classes that form a shared class-loading space. The complete class-loading picture must also include the parent class loader used by UnifiedClassLoader3s, as well as class loaders introduced for scoping and other specialty class-loading purposes. Figure 2.5 shows an outline of the class hierarchy that would exist for an EAR deployment containing EJBs and WARs. Figure 2.5. A complete class loader view.The following points apply to Figure 2.5:
In its current form, there are some advantages and disadvantages to the JBoss class-loading architecture. Advantages include the following:
Disadvantages include the following:
JBoss XMBeansXMBeans are the JBoss JMX implementation version of the JMX model MBean. XMBeans have the richness of the dynamic MBean metadata without the tedious programming required by a direct implementation of the DynamicMBean interface. The JBoss model MBean implementation allows you to specify the management interface of a component through an XML descriptor, hence the X in XMBean. In addition to providing a simple mechanism for describing the metadata required for a dynamic MBean, XMBeans also allow for the specification of attribute persistence, caching behavior, and even advanced customizations such as the MBean implementation interceptors. The high-level elements of the jboss_xmbean_1_2.dtd for the XMBean descriptor are shown in Figure 2.6. Figure 2.6. An overview of the JBoss 1.0 XMBean DTD (jboss_xmbean_1_2.dtd).
The mbean element is the root element of the document containing the required elements for describing the management interface of one MBean (constructors, attributes, operations, and notifications). It also includes an optional description element, which can be used to describe the purpose of the MBean, as well as an optional descriptors element, which allows for persistence policy specification, attribute caching, and so on. DescriptorsThe descriptors element contains all the descriptors for a containing element, as subelements. The descriptors suggested in the JMX specification as well as those used by JBoss have predefined elements and attributes, whereas custom descriptors have a generic descriptor element with the name and value attributes shown in Figure 2.7. Figure 2.7. The descriptors element content model.
The key descriptors child elements include the following:
Note that any of the constructor, attribute, operation, or notification elements may have a descriptors element to specify the specification-defined descriptors as well as arbitrary extension descriptor settings. The Management ClassThe class element specifies the fully qualified name of the managed object whose management interface is described by the XMBean descriptor. The ConstructorsThe constructor element(s) specifies the constructors available for creating an instance of the managed object. The constructor element and its content model are shown in Figure 2.8. Figure 2.8. The XMBean constructor element and its content model.
These are the key child elements:
The AttributesThe attribute element(s) specifies the management attributes exposed by the MBean. The attribute element and its content model are shown in Figure 2.9. Figure 2.9. The XMBean attribute element and its content model.The attribute element attributes include the following:
The OperationsThe management operations exposed by the XMBean are specified via one or more operation elements. The operation element and its content model are shown in Figure 2.10. Figure 2.10. The XMBean operation element and its content model.
The impact attribute defines the impact of executing the operation and must be one of the following:
These are the child elements:
NotificationsThe notification element(s) describes the management notifications that may be emitted by the XMBean. The notification element and its content model are shown in Figure 2.11. Figure 2.11. The XMBean notification element and content model.
These are the child elements:
|