Java - ClassLoader (类加载器)

119 阅读4分钟

1. 加载机制

  虚拟机把标识类的class文件加载到内存,经过校验,转换解析,初始化,最终形成可以被虚拟机直接使用的java类型。

1.1. 类加载的时机

  1. 使用new关键字实例化对象的时,读取一个类的静态字段的时,调用类的静态方法时。
  2. 使用java.lang.reflect包的方式对类进行反射调用时。
  3. 初始化类,发现父类还未初始化,需要对父类进行初始化。

1.2. 类加载的过程

  1. 加载。通过类的权限定名获取到定义此类的二进制字节流,将字节流所代表的静态存储结构转化成方法区的运行时数据结构,在方法区生成这个类的java.lang.Class对象。加载阶段个链接阶段的部分内容是交叉进行的。用户可以通过自己写的类加载器去控制字节流的获取方式(重写类的加载器的loadClass()方法)。
  2. 验证。是连接阶段的第一步。目的是确保class文件中的二进制字节流负荷虚拟机的要求,不会危及虚拟机自身安全,包括文件格式验证、元数据验证、字节码验证。
  3. 准备。连接阶段的第二步。正式为类变量分配内存空间和设置初始值的阶段。这个初始值和初始化阶段的赋值不同,这里指的是变量的默认初始值。另外,如果是final修饰的变量,在准备阶段赋予代码里指定的初始值。
  4. 解析。连接第三步,虚拟机将符号引用替换为直接引用过程。
  5. 初始化。根据程序代码去初始化类变量和其他资源。

1.3. 类加载器

  被不同加载器加载的同类名,也认为不同的类。双亲委派模型。两种类加载器:

  1. 启动加载器,虚拟机自身一部分。
  2. 所有的其他类加载器,这些加载器都由java语言实现。独立于虚拟机外部,全部继承自java.lang.ClassLoader抽象类。

  类加载器具体层次关系: 启动类加载器 Bootstrap ClassLoader  > 扩展类加载器 Extendsion ClassLoader  > 系统类加载器 Application ClassLoader  > 自定义类加载器。每一个类的加载,会优先由父加载器来加载。这种方式称为双亲委派。

1.3.1. Bootstrap ClassLoader

启动类加载器

  这个类加载器负责将\lib目录下的类库加载到虚拟机内存中,用来加载java的核心库,此类加载器并不继承于 java.lang.ClassLoader ,不能被java程序直接调用,代码是使用C++编写的.是虚拟机自身的一部分。

1.3.2. Extendsion ClassLoader

扩展类加载器

  这个类加载器负责加载 \lib\ext 目录下的类库,用来加载java的扩展库,开发者可以直接使用这个类加载器。

1.3.3. Application ClassLoader

系统类加载器

  这个类加载器负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的java类都是由这个类加载器加载,这个类加载器是CLassLoader中的 getSystemClassLoader() 方法的返回值,所以也称为系统类加载器。一般情况下这就是系统默认的类加载器。

2. 双亲委派模型

  双亲委派模型是一种组织类加载器之间关系的一种规范,他的工作原理是:如果一个类加载器收到了类加载的请求,它不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,这样层层递进,最终所有的加载请求都被传到最顶层的启动类加载器中,只有当父类加载器无法完成这个加载请求(它的搜索范围内没有找到所需的类时),才会交给子类加载器去尝试加载。
  这样的好处是:java类随着它的类加载器一起具备了带有优先级的层次关系。例如j: java.lang.Object 存放在 \jre\lib\rt.jar 中,它是所有java类的父类,因此无论哪个类加载都要加载这个类,最终所有的加载请求都汇总到顶层的启动类加载器中,因此Object类会由启动类加载器来加载,所以加载的都是同一个类,如果不使用双亲委派模型,由各个类加载器自行去加载的话,系统中就会出现不止一个Object类,应用程序就会全乱了。

Class.forname()

  是一个静态方法,最常用的是 Class.forname(String className) 根据传入的类的全限定名返回一个Class对象,该方法在将Class文件加载到内存的同时,会执行类的初始化。

Class.forName("com.wang.HelloWorld");

ClassLoader.loadClass()

  这是一个实例方法,需要一个ClassLoader对象来调用该方法,该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化。该方法因为需要得到一个ClassLoader对象,所以可以根据需要指定使用哪个类加载器。

ClassLoader cl=...;

cl.loadClass("com.wang.HelloWorld");