Java - ClassLoader

有哪些ClassLoader

  • Bootstrap ClassLoader
  • Extension ClassLoader
  • App ClassLoader

Bootstrap ClassLoader

  • 类名:BootstrapClassLoader
  • Bootstrap ClassLoader不是Java Class(其他的ClassLoader,包括Extension ClassLoader都是Java类),而是JVM虚拟机中实现的类加载器,用C或者C++写的。
  • 负责加载$JAVA_HOME/jre/lib/目录下的class,如$JAVA_HOME/jre/lib/rt.jar。这些classes可以称为"Bootstrap classes"
  • 通过非标准参数-Xbootclasspath可以改变Bootstrap classes的位置。但是绝大部分情况下,是没必要的

Extension ClassLoader

  • 类名:ExtClassLoader,继承自ClassLoader
  • 加载$JAVA_HOME/jre/lib/ext/目录下的class
  • 如果ext目录下的jar包中有相同路径的class,那么这个class是没法被加载的

如:

smart-extension1_0.jar 有class: smart.extension.Smart
smart-extension1_1.jar 同样有class: smart.extension.Smart
那么,smart.extension.Smart是undefined

image.png

App ClassLoader

  • 类名:AppClassLoader,父ClassLoader是ExtClassLoader
  • 加载classpath中的class,或者是java.class.path属性定义目录下的class。
  • java.class.path的默认值是'.',也就是当前目录
image.png

如果com.mypackage.MyClass在/classes/目录下,那么/classes/目录必须在classpath中。如果该class在myclasses.jar中,那么myclasses.jar也必须在classpath中。

那么,classpath如何指定呢?

  • 默认是".",也就是当前目录
  • 设置CLASSPATH环境变量
  • 通过-cp或者-classpath参数指定。这种方式会覆盖默认值和CLASSPATH设定的值
  • 通过-jar参数指定。这个会覆盖其他所有的值。

如:

java -jar spring-boot.jar

没有通过-cp/-classpath指定classpath,也没有-Djava.class.path参数,因此,默认是当前目录,也就是加载spring-boot.jar中的class

查看ClassLoader

Class类中提供了getClassLoader()方法来查看某个类的ClassLoader。简单的例子如下:

public class App 
{
    public static void main( String[] args ) {
        System.out.println("App's ClassLoader is: " + App.class.getClassLoader());
        ClassLoader parent = App.class.getClassLoader().getParent();
        System.out.println("AppClassLoader's parent is:" + parent);

        InnerClass innerClass = new InnerClass();
        System.out.println("InnerClass's ClassLoader is:" + innerClass.getClass().getClassLoader());
    }
}

输出为:

App's ClassLoader is: sun.misc.Launcher$AppClassLoader@279f2327
AppClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@452b3a41
InnerClass's ClassLoader is:sun.misc.Launcher$AppClassLoader@279f2327

Java类加载机制

英文术语

Java类加载机制:Java Class Loading Mechanism
父ClassLoader: parent class loader

所有自定义的ClassLoader都继承自java.lang.ClassLoaderjava.lang.ClassLoader的构造函数允许指定父ClassLoader。如果不指定,则使用system class loader。

public abstract class ClassLoader {
    //parent class loader默认为system class loader
    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }
    //当然,也可以指定parent class loader
    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }
}

class在loadClass(String name)方法中被加载。加载逻辑是:

  1. 如果class已经被加载了,直接return
  2. 如果class还没被加载,让父ClassLoader去加载。当然,如果父ClassLoader也有parent,则会继续把加载任务交给其父ClassLoader(就是让爸爸的爸爸去实际干活)。
  3. 如果父ClassLoader找不到class,则自定义的ClassLoader才开始加载class。
  4. 如果都找不到,抛出ClassNotFoundException

这就是所谓的“双亲委托加载模型”。

源代码为:

protected Class loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

为什么要委托给parent ClassLoader加载呢?
这是因为,一个Class在JVM中,是通过而元素来标识的,一个是ClassLoader,一个是类自己的全路径类名。
举个例子,有个类com.my.TestClass和一个自定义的加载器TestClassLoader(如不指定父ClassLoader,那么其parent为AppClassLoader)

  • 如果TestClassTestClassLoader加载,这标识为
  • 如果TestClassAppClassLoader加载,这标识为

那么问题来了,同一个类com.my.TestClass由不同的ClassLoader加载,对于JVM来讲,是不同的类

假如有类似如下代码:

TestClass A = ...;  //由TestClassLoader加载
TestClass B = ...;  //由AppClassLoader加载

A = B;  //报错!抛出ClassCastException

上面的类由不同的ClassLoader加载,执行到A=B时,会出现类似如下报错:

java.lang.ClassCastException: com.my.TestClass can not be cast to com.my.TestClass

因此,同一个Class由同一个ClassLoader加载是非常必要的。优先由parent ClassLoader加载就可以避免上述问题。

什么时候加载Class

参见:Java - 什么时候加载class

参考

  • 官网 - How Classes are Found

你可能感兴趣的:(Java - ClassLoader)