类加载器的分类

  • JVM支持两种类型的类加载器,分别是引导类加载器(BootStrap ClassLoader)和自定义类加载器(User-defined ClassLoader)
  • 从概念上讲,自定义类加载器一般指的是程序中由开发人员自定义的一类加载器,但是Java虚拟机规范却又没这么定义,而是将所有派生于抽象类的ClassLoader的类加载器划分为自定义类加载器

启动类加载器(引导类加载器)

Bootstrap ClassLoader

  • 这个类加载器使用C/C++语言实现的,嵌套在JVM内部
  • 它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
  • 并不继承自java.lang.ClassLoader,没有父加载器
  • 加载扩展类和应用程序类加载器,并指定为他们的父类加载器
  • 出于安全考虑,Bootstrap启动类加载器只加载包java、javax、sun等开头的类

扩展类加载器(Extension ClassLoader)

  • Java语言编写,由sun.misc.Launcer$ExtClassLoader实现
  • 派生于ClassLoader类
  • 父类加载器为启动类加载器
  • 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下(扩展目录)下加载类库。如果用户创建的jar放在此目录下,也会自动由扩展类加载器加载

应用程序类加载器(系统类加载器)

  • java语言编写,由sun.misc.Launcher$AppClassLoader实现
  • 派生于ClassLoader类
  • 父类加载器为扩展类加载器
  • 他负责加载环境变量classpath或系统属性,java.class.path指定路径下的类库
  • 该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由他来完成加载的
  • 通过ClassLoader#getSystemClassLoader()方法可以获取该类加载器

用户自定义加载器

  • 在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要的时候,我们可以自定义类加载器,来定制类的加载方式
  • 为什么要自定义类加载器?
    • 隔离加载类
    • 修改类加载的当时
    • 扩展加载源
    • 防止源码泄露

双亲委派机制

默认情况下,一个限定名的类只会被一个类加载器加载并解析使用,这样在程序中,它就是唯一的,不会产生歧义。

工作原理

  1. 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行
  2. 如果父类加载器还存在其父类加载器,则会进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器
  3. 如果父类加载器可以完成类加载任务,就成功返回,如果父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式
1
2
3
4
5
6
7
8
9
10
package java.lang;

public class String{
static{
System.out.println("自定义的String类的静态代码");
}
public static void main(String[] agrs){
System.out.println("Hello! My String");
}
}

会出现编译错误:

在java.lang.String中找不到main方法

原因:

使用的是引导类加载器

核心API String里面的是没有main方法的

  • 在JVM中 表示两个class对象是否为同一个类存在两个必要条件:
    • 类的完整类名必须一致,包括包名
    • 加载这个类的ClassCLoader(指ClassLoader实例对象)必须相同
  • 换句话说,在JVM中,即使这个两个类对象(class对象)来源同一个Class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不同,那么这两个类对象也是不相等的

对类加载器的引用

JVM必须知道一个类型是由启动类加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用的时候,JVM需要保存这两个类型的类加载器相同