The JBoss JMX Implementation Architecture


Let's now look at the JBoss JMX implementation.

The JBoss ClassLoader Architecture

JBoss 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 Java

Class 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 Type

A 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 Loaders
 package 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 Examples
 port 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:

[View full width]

[examples]$ ant -Dchap=chap2 -Dex=0c run-example ... [java] [ERROR,UCL1] Failed to invoke ExCtx.useValue [java] java.lang.reflect.InvocationTargetException [java] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) [java] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) [java] at sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:25) [java] at java.lang.reflect.Method.invoke(Method.java:324) [java] at org.jboss.chap2.ex0.ExCCEc.main(ExCCEc.java:58) [java] Caused by: java.lang.ClassCastException [java] at org.jboss.chap2.ex0.ExCtx.useValue(ExCtx.java:44) [java] ... 5 more

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 Not

A 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 Loaders
 package 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 Are

Loading 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 Constraints
 class <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 LinkageError
 package 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:

[View full width]

[examples]$ ant -Dchap=chap2 -Dex=0e run-example Buildfile: build.xml ... [java] java.lang.LinkageError: loader constraints violated when linking org/jboss/chap2 /ex0/ExObj2 class [java] at org.jboss.chap2.ex0.ExCtx.<init>(ExCtx.java:24) [java] at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) [java] at sun.reflect.NativeConstructorAccessorImpl.newInstance (NativeConstructorAccessorImpl.java:39) [java] at sun.reflect.DelegatingConstructorAccessorImpl.newInstance (DelegatingConstructorAccessorImpl.java:27) [java] at java.lang.reflect.Constructor.newInstance(Constructor.java:274) [java] at java.lang.Class.newInstance0(Class.java:308) [java] at java.lang.Class.newInstance(Class.java:261) [java] at org.jboss.chap2.ex0.ExLE.main(ExLE.java:53)

As expected, a LinkageError is thrown during the validation of the loader constraints required by line 24 of the ExCtx constructor.

Debugging Class-Loading Issues

Debugging 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 Class
 Class 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 Architecture

Now 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:

1.

Check the UnifiedLoaderRepository3 classes cache associated with the UnifiedClassLoader3. If the class is found in the cache, it is returned.

2.

If the class is not found, ask the UnfiedClassLoader3 if it can load the class. This is essentially a call to the superclass URLClass-Loader.loadClass(String,boolean) method to see if the class is among the URLs associated with the class loader or visible to the parent class loader. If the class is found, it is placed into the repository classes cache and returned.

3.

If the class is not found, the repository is queried for all UCLs that are capable of providing the class based on the repository package name-to-UCL map. When a UCL is added to a repository, an association between the package names available in the URLs associated with the UCL is made, and a mapping from package names to the UCLs with classes in the package is updated. This allows for a quick determination of which UCLs are capable of loading the class. The UCLs are then queried for the requested class, in the order in which the UCLs were added to the repository. If a UCL that can load the class is found, it is returned; otherwise, a java.lang.ClassNotFoundException is thrown.

Viewing Classes in the Loader Repository

Another 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 Classes

If 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 Model

The 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:

  • System ClassLoaders The System ClassLoaders node refers to either the thread context class loader (TCL) of the VM main thread or of the thread of the application that is loading the JBoss server, if it is embedded.

  • ServerLoader The ServerLoader node refers to a URLClassLoader that delegates to the System ClassLoaders and contains the following boot URLs:

    • All URLs referenced via the jboss.boot.library.list system property. These are path specifications relative to the libraryURL defined by the jboss.lib.url property. If there is no jboss.lib.url property specified, it defaults to jboss.home.url+/lib/. If there is no jboss.boot.library property specified, it defaults to jaxp.jar, log4j-boot.jar, jboss-common.jar, and jboss-system.jar.

    • The JAXP JAR, which is either crimson.jar or xerces.jar, depending on the -j option to the Main entry point. The default is crimson.jar.

    • The JBoss JMX JAR and GNU regex JAR, jboss-jmx.jar and gnu-regexp.jar.

    • The Oswego concurrency class's JAR, concurrent.jar.

    • Any JARs specified as libraries via -L command-line options.

    • Any other JARs or directories specified via -C command-line options.

  • Server The Server node represents a collection of UCLs created by the org.jboss.system.server.Server interface implementation. The default implementation creates UCLs for the patchDir entries as well as the server conf directory. The last UCL created is set as the JBoss main thread context class loader. This will be combined into a single UCL now that multiple URLs per UCL are supported.

  • All UnifiedClassLoader3s The All UnifiedClassLoader3 node represents the UCLs created by deployers. This covers EARs, JARs, WARs, SARs, and directories seen by the deployment scanner, as well as JARs referenced by their manifests and any nested deployment units they may contain. This is a flat namespace, and there should not be multiple instances of a class in different deployment JARs. If there are, only the first one loaded will be used, and the results may not be as expected. There is a mechanism for scoping visibility based on EAR deployment units, which is discussed earlier in this chapter, in the section "Scoping Classes." You can use this mechanism if you need to deploy multiple versions of a class in a given JBoss server.

  • EJB DynClassLoader The EJB DynClassLoader node is a subclass of URLClassLoader that is used to provide RMI dynamic class loading via the simple HTTP WebService. It specifies an empty URL[] and delegates to the TCL as its parent class loader. If the WebService is configured to allow system-level classes to be loaded, all classes in the UnifiedLoaderRepository3 as well as the system classpath are available via HTTP.

  • EJB ENCLoader The EJB ENCLoader node is a URLClassLoader that exists only to provide a unique context for an EJB deployment's java:comp JNDI context. It specifies an empty URL[] and delegates to the EJB DynClassLoader as its parent class loader.

  • Web ENCLoader The Web ENCLoader node is a URLClassLoader that exists only to provide a unique context for a web deployment's java:comp JNDI context. It specifies an empty URL[] and delegates to the TCL as its parent class loader.

  • WAR Loader The WAR Loader is a servlet container-specific class loader that delegates to the Web ENCLoader as its parent class loader. The default behavior is to load from its parent class loader and then the WAR WEB-INF classes and lib directories. If the servlet 2.3 class-loading model is enabled, it will first load from its WEB-INF directories and then the parent class loader.

In its current form, there are some advantages and disadvantages to the JBoss class-loading architecture. Advantages include the following:

  • Classes do not need to be replicated across deployment units in order to have access to them.

  • Many future possibilities including novel partitioning of the repositories into domains, dependency and conflict detection, and so on.

Disadvantages include the following:

  • Existing deployments may need to be repackaged to avoid duplication of classes. Duplication of classes in a loader repository can lead to class cast exceptions and linkage errors, depending on how the classes are loaded.

  • Deployments that depend on different versions of a given class need to be isolated in separate EARs, and a unique HeirarchicalLoaderRepository3 needs to be defined, using a jboss-app.xml descriptor.

JBoss XMBeans

XMBeans 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.

Descriptors

The 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:

  • interceptors The interceptors element specifies a customized stack of interceptors that will be used in place of the default stack. Currently, this is used only when specified at the MBean level, but it could define a custom attribute or operation-level interceptor stack in the future. The content of the interceptors element specifies a custom interceptor stack. If no interceptors element is specified, the standard ModelMBean interceptors will be used. These are the standard interceptors:

    • org.jboss.mx.interceptor.PersistenceInterceptor

    • org.jboss.mx.interceptor.MBeanAttributeInterceptor

    • org.jboss.mx.interceptor.ObjectReferenceInterceptor

    When specifying a custom interceptor stack, you typically include the standard interceptors, along with your own, unless you are replacing the corresponding standard interceptor.

    Each interceptor element content value specifies the fully qualified classname of the interceptor implementation. The class must implement the org.jboss.mx.interceptor.Interceptor interface. The interceptor class must also have either a no-arg constructor or a constructor that accepts a javax.management.MBeanInfo.

    The interceptor elements may have any number of attributes that correspond to JavaBeans-style properties on the interceptor class implementation. For each interceptor element attribute specified, the interceptor class is queried for a matching setter method. The attribute value is converted to the true type of the interceptor class property, using the java.beans.PropertyEditor associated with the type. It is an error to specify an attribute for which there is no setter or PropertyEditor.

    • persistence The persistence element allows the specification of the persistPolicy, persistPeriod, persistLocation, and persistName persistence attributes suggested by the JMX specification. The following are the persistence element attributes:

    • persistPolicy The persistPolicy attribute defines when attributes should be persisted, and its value must be one of the following:

      NeverAttribute values are transient values that are never persisted.

      OnUpdateAttribute values are persisted whenever they are updated.

      OnTimerAttribute values are persisted based on the time given by persistPeriod.

      NoMoreOftenThanAttribute values are persisted when updated but no more often than persistPeriod.

    • persistPeriod The persistPeriod attribute gives the update frequency in milliseconds if the persistPolicy attribute is NoMoreOftenThan or OnTimer.

    • persistLocation The persistLocation attribute specifies the location of the persistence store. Its form depends on the JMX persistence implementation. Currently, this should refer to a directory into which the attributes will be serialized if using the default JBoss persistence manager.

    • persistName The persistName attribute can be used in conjunction with the persistLocation attribute to further qualify the persistent store location. For a directory persistLocation, persistName specifies the file to which the attributes are stored within the directory.

  • currencyTimeLimit The currencyTimeLimit element specifies the time in seconds for which a cached value of an attribute remains valid. Its value attribute gives the time in seconds. A value of 0 indicates that an attribute value should always be retrieved from the MBean and never cached. A value of -1 indicates that a cache value is always valid.

  • display-name The display-name element specifies the human-friendly name of an item.

  • default The default element specifies a default value to use when a field has not been set. Note that this value is not written to the MBean on startup, as is the case with the jboss-service.xml attribute element content value. Rather, the default value is used only if there is no attribute accessor defined and there is no value element defined.

  • value The value element specifies a management attribute's current value. Unlike the default element, the value element is written through to the MBean on startup, provided that there is a setter method available.

  • persistence-manager The persistence-manager element gives the name of a class to use as the persistence manager. The value attribute specifies the classname that supplies the org.jboss.mx.persistence.PersistenceManager interface implementation. The only implementation currently supplied by JBoss is org.jboss.mx.persistence.ObjectStreamPersistenceManager, which serializes the ModelMBeanInfo content to a file by using Java serialization.

  • descriptor The descriptor element specifies an arbitrary descriptor not known to JBoss. Its name attribute specifies the type of the descriptor, and its value attribute specifies the descriptor value. The descriptor element allows for the attachment of arbitrary management metadata.

  • injection The injection element describes an injection point for receiving information from the microkernel. Each injection point specifies the type and the set method to use to inject the information into the resource. The injection element supports these type attributes:

    • id The id attribute specifies the injection point type. These are the current injection point types:

      MBeanServerTypeAn MBeanServerType injection point receives a reference to the MBeanServer that the XMBean is registered with.

      MBeanInfoTypeAn MBeanInfoType injection point receives a reference to the XMBean ModelMBeanInfo metadata.

      ObjectNameTypeThe ObjectName injection point receives the ObjectName that the XMBean is registered under.

    • setMethod The setMethod attribute gives the name of the method used to set the injection value on the resource. The set method should accept values of the type corresponding to the injection point type.

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 Class

The class element specifies the fully qualified name of the managed object whose management interface is described by the XMBean descriptor.

The Constructors

The 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:

  • description This element provides a description of the constructor.

  • name This element specifies the name of the constructor, which must be the same as the implementation class.

  • parameter The parameter element describes a constructor parameter. The parameter element has the following attributes:

    • description An optional description of the parameter.

    • name The required variable name of the parameter.

    • type The required fully qualified classname of the parameter type.

  • descriptors This element specifies any descriptors to associate with the constructor metadata.

The Attributes

The 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:

  • access The optional access attribute defines the read/write access modes of an attribute. It must be one of the following:

    • read-only The attribute may only be read.

    • write-only The attribute may only be written.

    • read-write The attribute is both readable and writable. This is the implied default.

  • getMethod The getMethod attribute defines the name of the method that reads the named attribute. This must be specified if the managed attribute should be obtained from the MBean instance.

  • setMethod The setMethod attribute defines the name of the method that writes the named attribute. This must be specified if the managed attribute should be obtained from the MBean instance. The key child elements of the attribute element include the following:

    • description A description of the attribute.

    • name The name of the attribute, as would be used in the MBeanServer.getAttribute() operation.

    • type The fully qualified classname of the attribute type.

    • descriptors Any additional descriptors that affect the attribute persistence, caching, default value, and so on.

The Operations

The 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:

  • ACTION The operation changes the state of the MBean component (write operation).

  • INFO The operation should not alter the state of the MBean component (read operation).

  • ACTION_INFO The operation behaves like a read/write operation.

These are the child elements:

  • description This element specifies a human-readable description of the operation.

  • name This element contains the operation's name.

  • parameter This element describes the operation's signature.

  • return-type This element contains a fully qualified classname of the return type from this operation. If it is not specified, it defaults to void.

  • descriptors This element specifies any descriptors to associate with the operation metadata.

Notifications

The 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:

  • description This element gives a human-readable description of the notification.

  • name This element contains the fully qualified name of the notification class.

  • notification-type This element contains the dot-separated notification type string.

  • descriptors This element specifies any descriptors to associate with the notification metadata.



JBoss 4. 0(c) The Official Guide
JBoss 4.0 - The Official Guide
ISBN: B003D7JU58
EAN: N/A
Year: 2006
Pages: 137

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net