Java class loaders

Often when somebody starts to learn Java, he meets the problems associated with classes loading. These problems are mostly always caused by class loaders described more in details in this article.

At the begin, we'll describe the role of class loaders and their way of working. After that, we'll write some JUnit test cases to prove some of class loaders features.

Class loaders in Java

As the name indicates, class loader is something that loads a Java class. To be more precise, class loader reads the content of class file and then uses the read byte array to create an instance of java.lang.Class. How does class loader know where to find the .class (or archive) files ? It can do that thanks to system and running program configuration. System configuration means the defined path variables, as JAVA_HOME. Program configuration means here the classes loaded by given program, as for example program manifest file or simple Java's class with specified classpath parameter (-cp).

Three default types of class loader exist:

  1. Bootstrap - known also as Primordial ClassLoader, is the root of class loaders, it's responsible for loading basic Java classes as String, Map, directly from rt.jar. Because it's the root of class loaders, the invocation of getClassLoader() on one of classes loaded by bootstrap, will return a null.
  2. Extension - the single child of Bootstrap. When invoked, it delegates first the class loading to its parent. If the parent isn't able to resolve the class, Extension tries to do it by itself. It'll look for demanded classes inside directory (or directories) associated to java.ext.dirs system property (for example: jre7/lib/ext) . An example of Extension class loader is sun.misc.Launcher$ExtClassLoader.
  3. Application - it's in charge of loading classes specified in classpath parameter (-cp), CLASSPATH environment variable or Class-Path attribute of Manifest file inside JAR. An example of Application class loader is sun.misc.Launcher$AppClassLoader.

Class loader works in accordance to 3 principles:

We can implement our customized class loader by extending java.lang.ClassLoader class. Its main methods to know are:
- defineClass: with its different signatures, it's used to convert byte[] array read from class file to java.lang.Class instance.
- findClass: this method is in charge to find the class through provided binary name (the same as used for import classes through import statement). The method returns a Class instance, so it should invoke defineClass internally.
- getParent: used to return parent class loader.
- loadClass: this method loads class. Default implementation of this method tries first to check if demanded class is already loaded (through findLoadedClass method). If the class isn't found, loadClass of parent class loader is invoked. If the parent is null, class loader of JVM is used. If the class remains not found, findClass method is called.
- resolveClass: when loaded class will be used to create new objects, it must be resolved. Without the call of this method, JVM could only make the checks if given class exists.

Class loader example

As told previously, it's now the turn of JUnit tests for class loading issues. Every case is commented and they won't be any supplementary comments after the test code:

public class ClassloaderTest {

  @Test
  public void testClassloaderTypes() {
    String test = "test";
    assertTrue("String's classloader should be null (it means that bootstrap classloader was used)", 
      test.getClass().getClassLoader() == null);
    
     /**
      * Before selecting a class for extension classloader, check where the 
      * extensions are stored. You can do it by looking at JARs placed in 
      * directory(-ies) specified in java.ext.dirs system property. 
      * 
      * To check the extension classloader read the JARs and try to initialize classes 
      * included inside them.
      */
    System.out.println("ext.dir = "+System.getProperty("java.ext.dirs"));
    
    DNSNameService dnsService  = new DNSNameService();
    assertTrue("DNSNameService should be loaded through extension class loader", 
      dnsService.getClass().getClassLoader().toString().toLowerCase().contains("extclassloader"));

    InnerClass innerClass = new InnerClass();
    assertTrue("InnerClass should be loaded through application class loader", 
      innerClass.getClass().getClassLoader().toString().toLowerCase().contains("appclassloader"));
  }
        
  /**
    * Before launching this test, you must copy loaded .class file to directory specified in setJarsLocation method. The
    * lookup isn't recursive (doesn't include subdirectories).
    */
  @Test
  public void testFixedLocatedClassLoader() {
    /**
     * First, we want to check if the customized classloader is able to load demanded .class files.
     */
    FixedLocatedClassLoader fixedLoader = new FixedLocatedClassLoader();
    fixedLoader.addAppliedPackages("com.waitingforcode");
    fixedLoader.setClassLocation("/home/bartosz/tmp/classes/");
    
    // Class must be loaded with binary-name (the same name is used to import a class in .java files)
    Class<?> welcomerClass = Class.forName("com.waitingforcode.bean.WelcomerTmp", 
      true,  fixedLoader);
    assertTrue("Loaded class should be 'com.waitingforcode.bean.WelcomerTmp' but was '"+
      welcomerClass.getCanonicalName()+"'", 
      welcomerClass.getCanonicalName().equals("com.waitingforcode.bean.WelcomerTmp"));
    
    // Try to construct an object with constructor retrieved from loaded class
    Constructor<?> constructor = welcomerClass.getConstructor();
    String welcomer = constructor.newInstance().getClass().getCanonicalName();
    assertTrue("Constructed object should be of type 'com.waitingforcode.bean.WelcomerTmp' but was '"+welcomer+"'", 
      welcomer.equals("com.waitingforcode.bean.WelcomerTmp"));

     /**
      * Now, we can try to load an non-existent class and check if ClassNotFoundException is thrown.
      */
    Class<?> nonExistentClass = null;
    boolean wasCnfe = false;
    try {
      nonExistentClass = Class.forName("com.waitingforcode.bean.NonExistentWelcomer", 
        true, fixedLoader);
    } catch (ClassNotFoundException cnfe) {
      wasCnfe = true;
    }

    assertTrue("ClassNotFoundException should be thrown for non-existent class but wasn't", 
      wasCnfe);
    assertTrue("NonExistentWelcomer class shouldn't be loaded but it was ("+nonExistentClass+")", 
      nonExistentClass == null);
  }
        
  @Test
  public void testVisibility() {
     /**
      * To test the visibility, we'll load a class with Application class loader first. 
      * After that, we'll try to read the same class with Extension class loader. 
      * The second try should fail.
      */ 
      ClassLoader appClassLoader = InnerClass.class.getClassLoader();
      assertTrue("Application class loader shouldn't be null", appClassLoader != null);
      ClassLoader extClassLoader = appClassLoader.getParent();
      assertTrue("Application class loader should have Extension class loader for parent, but it hasn't ("+extClassLoader+" found instead)", 
        extClassLoader.getClass().toString().toLowerCase().contains("extclassloader"));
      
      // this shouldn't fail
      Class<?> welcomer = Class.forName("com.waitingforcode.bean.Welcomer", true, appClassLoader);
      assertTrue("Retreived class name should be 'com.waitingforcode.bean.Welcomer' but was '"+welcomer.getCanonicalName()+"'", 
        welcomer.getCanonicalName().equals("com.waitingforcode.bean.Welcomer"));
      
      // this should fail
      boolean wasCnfe = false;
      try {
        Class.forName("com.waitingforcode.bean.Welcomer", true, extClassLoader);
      } catch (ClassNotFoundException cnfe) {
        wasCnfe = true;
        cnfe.printStackTrace();
      }
      assertTrue("ClassNotFoundException should be thrown when retreiving com.waitingforcode.bean.Welcomer with Extension class loader, but it wasn't", 
        wasCnfe);
  } 
}

/**
 * Sample classloader based on fixed path location. Class loading 
 * isn't a recursive process, so all demanded classes must
 * be placed at the root level of storage directory. This loader 
 * applies only to packages defined in appliedPackages list.
 */
class FixedLocatedClassLoader extends ClassLoader {
        
  private String classLocation = "";
  private List<String> appliedPackages;
  
  private List<String> getAppliedPackages() {
    if (appliedPackages == null) {
      this.appliedPackages = new ArrayList<String>();
    }
    return this.appliedPackages;
  }
  
  public void addAppliedPackages(String packageName) {
          getAppliedPackages().add(packageName);
  }
  
  private boolean applyFixedLoad(String className) {
    for (String packageName : getAppliedPackages()) {
      if (className.contains(packageName)) return true;
    }
    return false;
  }
  
  public void setClassLocation(String classLocation) {
    this.classLocation = classLocation;
  }
        
  /**
   * findClass method tries to find .class files. According to the Javadoc 
   * (@link http://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html#findClass%28java.lang.String%29)
   * it should be invoked in loadClass() after trying to load given class in parent classloader.
   */
  @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {
    if (applyFixedLoad(name)) {
      // construct path with .class file
      String classFile = name.replaceAll("\\.", "/")+".class";
            
      // Opening the file
      byte[] classBytes = null;

      Path path = Paths.get(this.classLocation+classFile);
      classBytes = Files.readAllBytes(path);
      /**
       * Thanks to defineClass() we can convert retrieved array of bytes to Class instance. 
       * Each created Class instance has attached an instance of java.security.ProtectionDomain 
       * class. ProtectionDomain encapsulates a set of permissions regardless to security policies.
       */
      Class<?> searchedClass = defineClass(name, classBytes, 0, classBytes.length);
      return searchedClass;
      return null;
    }
    return super.findClass(name);
  }
        
  @Override
  protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
    if (!applyFixedLoad(name)) {
      return super.loadClass(name, resolve);
    }
   /**
    * We must call resolveClass(Class<?>) method. Note from loadClass method Javadoc:
    * "the class was found using the above steps, and the resolve flag is true, this method
    * will then invoke the resolveClass(Class) method on the resulting Class object."
    * 
    * Resolving a class means an operation that must be invoked every time when we'll 
    * need to construct an object of the loaded class or use one of its methods. Normally 
    * this method should be call only when resolve parameter of loadClass method is set to true. 
    * If we won't resolve a class, it can be used only to check purposes. For example, with not 
    * resolved class we could only verify if this class exists in the classpath (without 
    * using it to construct an object).
    */
    Class<?> searchedClass = findClass(name);
    if (resolve) {
      resolveClass(searchedClass);
    }
    return searchedClass;
  }
}

class InnerClass {}

And the sample output is:

Constructing !
java.nio.file.NoSuchFileException: /home/bartosz/tmp/classes/com/mysite/bean/NonExistentWelcomer.class
    at sun.nio.fs.WindowsException.translateToIOException(Unknown Source)
    at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
    at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
    at sun.nio.fs.WindowsFileAttributeViews$Basic.readAttributes(Unknown Source)
    at sun.nio.fs.WindowsFileAttributeViews$Basic.readAttributes(Unknown Source)
    at sun.nio.fs.WindowsFileSystemProvider.readAttributes(Unknown Source)
    at java.nio.file.Files.readAttributes(Unknown Source)
    at java.nio.file.Files.size(Unknown Source)
    at java.nio.file.Files.readAllBytes(Unknown Source)
    at com.mysite.test.FixedLocatedClassLoader.findClass(ClassloaderTest.java:171)
    at com.mysite.test.FixedLocatedClassLoader.loadClass(ClassloaderTest.java:203)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Unknown Source)
    at com.waitingforcode.test.ClassloaderTest.testFixedLocatedClassLoader(ClassloaderTest.java:79)
ext.dir = C:\Program Files (x86)\Java\jre7\lib\ext;C:\Windows\Sun\Java\lib\ext
java.lang.ClassNotFoundException: com.mysite.bean.Welcomer
  at java.net.URLClassLoader$1.run(Unknown Source)
  at java.net.URLClassLoader$1.run(Unknown Source)
  at java.security.AccessController.doPrivileged(Native Method)
  at java.net.URLClassLoader.findClass(Unknown Source)
  at java.lang.ClassLoader.loadClass(Unknown Source)
  at java.lang.ClassLoader.loadClass(Unknown Source)
  at java.lang.Class.forName0(Native Method)
  at java.lang.Class.forName(Unknown Source)
  at com.waitingforcode.test.ClassloaderTest.testVisibility(ClassloaderTest.java:108)

This time we discovered how classes are loaded. The load is made by one of three class loaders. They can be more of them when we creates a customized class loader that must extend ClassLoader class. We saw that they're 3 main principles to respect the correct way of reading classes: delegation, visibility and uniqueness. When one class is read twice, some runtime problems as NoSuchMethodError can occur.


If you liked it, you should read:

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!