JVM 类加载器

573 阅读3分钟

加载器

类加载是通过类加载器完成的。

有两种类型的类加载器

  • Java虚拟机自带的加载器

    • 根类加载器(Bootstrap)。
    • 扩展类加载器(Extension)。
    • 系统(应用)类加载器(System)。
  • 用户自定义的类加载器

    • java.lang.ClassLoader的子类。
    • 用户可以定制类的加载方式。

image.png

注:上图不是继承关系而是包含关系。

  • Bootstrap ClassLoader : 启动(根)类加载器,可以加载 $JAVA_HOME中jre/lib/rt.jar 里所有的class,由C++实现,不是ClassLoader子类,它没有父加载器,
  • Extension ClassLoader : 扩展类加载器负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中 jre/lib/*.jar-Djava.ext.dirs 指定目录下的jar包。
  • App ClassLoader :引用(系统)类加载器负责加载classpath中 指定的jar包及目录中class
  • 若有一个类加载器能够成功加载Test类(自己定义的类),那么这个类加载器被称为定义类加载器,所有能成功返回Class对象引用的类加载器(包括定义类加载器都被称为初始类加载器, 一般继承自 ClassLoader 类。

类加载器的双亲委托机制

  • 在双亲委托机制中,各个加载器按照父子关系形成了树形结构,除了根类加载器之外,其余的类加载器都有且只有一个父加载器。
  • 当一个类加载器想要加载某个类时,它自身不会立刻加载,而是会委托它的双亲(父亲)类加载器去加载。

image.png

来个样例,来证明不同的类加载器是加载特定目录下的class

  • 在开始之前先来阅读一番jdk源码。

image.png

  • 可知在一些情况下用 null 表示 Bootstrap ClassLoader
public class MyClassLoader{
      public static void main(String[] args) throws ClassNotFoundException {
            Class<?> stringClazz = Class.forName("java.lang.String");
            Class<?> myClazz = Class.forName("com.jvmstudy.classloading.MyClassLoader");
            System.out.println(stringClazz.getClassLoader());
            System.out.println("==========================");
            System.out.println(myClazz.getClassLoader());
      }
}
  • 结果

image.png

image.png

  • 自定义的类就由应用(App)类加载器加载。

类的加载

  • 类加载器并不需要等到某个类被“首次主动使用”时再加载它。
  • JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)。
  • 如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。

获取类的方式

  • 通过加载器获得类: loader.loaderClass(className)
  • 通过反射获得类:Class.forName(className)

写一些代码

验证各加载器的关系

  • 代码
public class ClassLoaderTest {
    public static void main(String[] args) {
        //获取系统类加载器,即应用(App)类加载器
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        System.out.println(classLoader.toString());
        while (classLoader!=null){
            classLoader = classLoader.getParent();//获取类加载器的父类加载器
            System.out.println(classLoader);
        }
    }
}
  • 结果

image.png

获取类的绝对路径

image.png

  • 代码
public class ClassLoaderTest2 {
    public static void main(String[] args) throws IOException {
        //返回此线程的上下文类加载器
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        String resourceName = "com/jvmstudy/classloading/ClassLoaderTest2.class";//类所在的相对路径
        Enumeration<URL> enumeration = classLoader.getResources(resourceName);//可以获取到类的绝对路径
        while (enumeration.hasMoreElements()){
           URL url = enumeration.nextElement();
            System.out.println(url);
        }
    }
}
  • 结果 image.png

获得ClassLoader的途径

  • 获得当前类的ClassLoader
    • clazz.getClassLoader(); //clazz是类,
  • 获得当前线程上下文的ClassLoader
    • Thread.currentThread().getContextClassLoader();
  • 获得系统的ClassLoader
    • ClassLoader.getSystemClassLoader();
  • 获得调用者的ClassLoader
    • DriverManager.getCallerClassLoader();

小结

  • 就一点,类是类对象是对象。
  • 一定要将类和对象区分开来。