011_类加载流程

2020-10-16   18 次阅读


类加载流程

当程序使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、链接、初始化三个步骤对该类进行类加载。

1.1 加载

类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象。类的加载过程是由类加载器来完成,类加载器由JVM提供。我们开发人员也可以通过继承ClassLoader来实现自己的类加载器。

1.2 链接

通过类的加载,内存中已经创建了一个Class对象。链接负责将二进制数据合并到 JRE中。链接需要通过验证、准备、解析三个阶段。

验证

验证阶段用于检查被加载的类是否有正确的内部结构,并和其他类协调一致。即是否满足java虚拟机的约束。

准备

类准备阶段负责为类的类变量分配内存,并设置默认初始值。

解析

引用其实对应于内存地址。在编写代码时使用引用方法,类并不知道这些引用方法的内存地址,因为类还未被加载到虚拟机中,无法获得这些地址。编译器只能用符号引用来指代要调用的方法。解析阶段的目的,就是将这些符号引用解析为实际引用。

1.3 初始化

类的初始化阶段,虚拟机主要对类变量进行初始化。虚拟机调用<clinit>方法,进行类变量的初始化。

  1. 虚拟机会收集类及父类中的类变量及类方法组合为<clinit>方法,根据定义的顺序进行初始化。虚拟机会保证子类的<clinit>执行之前,父类的<clinit>方法先执行完毕。
  2. 对于接口,只有真正使用父接口的类变量才会真正的加载父接口。这跟普通类加载不一样。
  3. 虚拟机会保证一个类的< clinit>方法在多线程环境中被正确地加锁和同步,如果多个线程同时去初始化一个类,那么只有一个线程去执行这个类的<clinit>方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>方法完毕。
  4. ClassLoader只会对类进行加载,不会进行初始化;使用Class.forName()会强制导致类的初始化。

1.4 类加载器

  1. 类加载器负责将.class文件(不管是jar,还是本地磁盘,还是网络获取等等)加载到内存中,并为之生成对应的java.lang.Class对象。
  2. 每个类在JVM中使用全限定类名(包名+类名)与类加载器联合为唯一的ID,所以如果同一个类使用不同的类加载器,可以被加载到虚拟机,但彼此不兼容。
  3. Bootstrap ClassLoader为根类加载器,负责加载java的核心类库。根加载器不是ClassLoader的子类,是有C++实现的。负责加载%JAVA_HOME%/jre/lib下的jar包
  4. Extension ClassLoader为扩展类加载器,负责加载%JAVA_HOME%/jre/ext或者java.ext.dirs系统熟悉指定的目录的jar包。
  5. System ClassLoader为系统(应用)类加载器,负责加载加载来自java命令的-classpath选项,如果没有特别指定,则用户自定义的类加载器默认都以系统类加载器作为父加载器。。
  6. 父类委托(双亲委派)加载机制:先让父加载器试图加载该Class,只有在父加载器无法加载时该类加载器才会尝试从自己的类路径中加载该类。

image.png

1.5 自定义类加载器

ClassLoader类有两个关键的方法:

  1. protected Class loadClass(String name, boolean resolve):name为类名,resove如果为true,在加载时解析该类。
  2. protected Class findClass(String name) :根据指定类名来查找类。

如果要实现自定义类,可以重写这两个方法来实现。但推荐重写findClass方法,而不是重写loadClass方法,因为loadClass方法内部会调用findClass方法。以下是load方法的加载流程

  1. 判断此类是否已经加载;
  2. 如果父加载器不为null,则使用父加载器进行加载;反之,使用根加载器进行加载;
  3. 如果前面都没加载成功,则使用findClass方法进行加载。

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议