jueves, 15 de agosto de 2013

Inspecting Weblogic classloaders (without using ws-cat)

I know Weblogic have a great application called ws-cat that you can use to diagnose classloading problems in the deployed applications. But, sometimes a find myself needing "a little more". I've had the necessity of knowing the classloaders involved in the classloader hierarchy and the classpath for each one...even if I know this is unsupported, that some of the involved classloaders have no documented behaviour,..etc.

I started with a typical EAR/WAR packaged Web application with a servlet, containing some code like this:

ClassLoader cl = this.getClass().getClassLoader();
while(cl != null) {
  System.out.println("Cl: " + cl + ", class: " + cl.getClass());
  cl = cl.getParent();
}

Invoking the servlet, I obtained this:

Cl: weblogic.utils.classloaders.ChangeAwareClassLoader@22dd997d finder: weblogic.utils.classloaders.CodeGenClassFinder@704fd3b4 annotation: TestClApp@TestClWeb, class: class weblogic.utils.classloaders.ChangeAwareClassLoader
Cl: weblogic.utils.classloaders.FilteringClassLoader@8d40807 finder: weblogic.utils.classloaders.CodeGenClassFinder@185b0818 annotation: TestClApp@TestClWeb, class: class weblogic.utils.classloaders.FilteringClassLoader
Cl: weblogic.utils.classloaders.GenericClassLoader@b39c954 finder: weblogic.utils.classloaders.CodeGenClassFinder@706c26 annotation: TestClApp@, class: class weblogic.utils.classloaders.GenericClassLoader
Cl: weblogic.utils.classloaders.FilteringClassLoader@54cc3303 finder: weblogic.utils.classloaders.CodeGenClassFinder@2a293573 annotation: , class: class weblogic.utils.classloaders.FilteringClassLoader
Cl: weblogic.utils.classloaders.GenericClassLoader@1f0fde7d finder: weblogic.utils.classloaders.CodeGenClassFinder@1db2cc30 annotation: , class: class weblogic.utils.classloaders.GenericClassLoader
Cl: sun.misc.Launcher$AppClassLoader@752a2259, class: class sun.misc.Launcher$AppClassLoader
Cl: sun.misc.Launcher$ExtClassLoader@21353d27, class: class sun.misc.Launcher$ExtClassLoader

Thus, the classloader hierarchy in my webapp is: ChangeAwareClassLoader -> FilteringClassLoader -> GenericClassLoader -> FilteringClassLoader -> GenericClassLoader -> AppClassLoader (JVM) -> ExtClassLoader (JVM).

But now I want to know if I can obtain some information from them. Using a little reflection, we can see the methods the classloaders offer us:

ClassLoader cl = this.getClass().getClassLoader();

while(cl != null) {
  System.out.println("Cl: " + cl + ", class: " + cl.getClass());
  if(cl.getClass().getName().contains("ChangeAwareClassLoader") || 
     cl.getClass().getName().contains("FilteringClassLoader") ||
     cl.getClass().getName().contains("GenericClassLoader")) {

    System.out.println("Methods:");
    Method[] methods = cl.getClass().getMethods();
    for(Method method : methods) {
   System.out.println("- " + method);
}
  }
  
  cl = cl.getParent();
}

Observing the output, yo can see all of the involved Weblogic classloaders offer the following method:

public java.lang.String weblogic.utils.classloaders.ChangeAwareClassLoader.getClassPath()

You can use again some reflection to invoke that method. If you do it, you can find the method returns the classpath for the classloader, with each entry separated by ';' (in Windows?). So, doing something like this, yo can obtain the classloader classpath.

ClassLoader cl = this.getClass().getClassLoader();
while(cl != null) {
  System.out.println("Cl: " + cl + ", class: " + cl.getClass());
  
  if(cl.getClass().getName().contains("ChangeAwareClassLoader") || 
     cl.getClass().getName().contains("FilteringClassLoader") ||
     cl.getClass().getName().contains("GenericClassLoader")) {
 
    System.out.println("Classpath:");
    Method method = cl.getClass().getMethod("getClassPath");
String classpath[] = ((String)method.invoke(cl)).split(";");
for(String cpEntry : classpath) {
  System.out.println(cpEntry);
}
  }
  
  cl = cl.getParent();
}

That gives us very cool information about our application runtime. For example we can check if the documentation is telling us the truth :P, which classloaders have the web module classes, which have the EJB module classes, which have the App-level classes, etc. It can be even more useful if you're using shared libraries and want to know what's really going on.

In my tests I used

ClassLoader cl = this.getClass().getClassLoader();

but maybe is more useful to use:

ClassLoader cl = Thread.currentThread().getContextClassLoader();

Luckily, for my scenario, the results are exactly the same.

As I said at the post start, you may not use the output of this code as a "source of truth", but just as a means to get more light in your diagnosing efforts. This is internal Weblogic material that I guess "we should not use". For example, each of the Weblogic classloaders, has a companion "finder" classloader with its own classpath; what does it does?

method = cl.getClass().getMethod("getFinderClassPath");
classpath = ((String)method.invoke(cl)).split(";");
for(String cpEntry : classpath) {
  System.out.println(cpEntry);
}



No hay comentarios: