思考类加载过程

什么是类加载?

类加载是一个将.class字节码文件实例化成Class对象并进行相关初始化过程。

​ 或者负责读取 Java 字节代码,并转换成java.lang.Class类的一个实例。

说说类的加载过程?

Java 的类加载器是一个运行时核心基础设施模块,主要在启动进行类的Load,LinkInit ,即加载,链接,初始化

加载(Load): **读取类文件产生二进制流,**并转化特定数据结构,(Class文件、ZIP包、运行时计算生成的

**链接 (Link)**包括(校验、准备、解析)三个步骤.

  • 校验:是更为详细的校验,确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。(详细、安全)
  • 准备:正式为类变量分配内存设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。
  • 解析:解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。(可以在初始化后进行)

初始化(Init): 执行类构造器 方法,如果赋值运算是通过其他类的静态方法完成的,那么会马上解析另一个类,在虚拟机栈中执行完毕后返回值进行赋值

ClassLoader的作用是啥?

ClassLoader 的使用是提前加载.class 类文件存在内存中。

类加载器种类

  • 启动类加载器,Bootstrap ClassLoader,加载JAVA_HOME\lib,或者被-Xbootclasspath参数限定的类

  • 扩展类加载器,Extension ClassLoader,加载\lib\ext,或者被java.ext.dirs系统变量指定的类

  • 应用程序类加载器,Application ClassLoader,加载ClassPath中的类库

  • 自定义类加载器,通过继承ClassLoader实现,一般是加载我们的自定义类

类加载器间的关系

我们进一步了解类加载器间的关系(并非指继承关系,而是组合),主要可以分为以下4点

启动类加载器,由C++实现,没有父类。

拓展类加载器(ExtClassLoader),由Java语言实现,父类加载器为null

系统类加载器(AppClassLoader),由Java语言实现,父类加载器为ExtClassLoader

自定义类加载器,父类加载器肯定为AppClassLoader。

面试官:那自己怎么去实现一个ClassLoader呢?请举个实际的例子

自己实现ClassLoader时只需要继承ClassLoader类,然后覆盖findClass(String name)方法即可完成一个带有双亲委派模型的类加载器。

自定义类加载器

JDK1.2之前,在自定义类加载时,总会去继承ClassLoader类并重写loadClass方法,从而实现自定义的类加载类,但是在JDK1.2之后已不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加载逻辑写在findClass()方法中,

如果要符合双亲委派规范,则重写findClass方法(用户自定义类加载逻辑);

要破坏的话,重写loadClass方法(双亲委派的具体逻辑实现)

双亲委派模型

我们可以由图看到以下过程:

  1. 自底向上检查类是否已经加载,若已加载,直接返回。
  2. 若所有父类都没有加载该类,则自顶向下尝试加载该类。
  3. 如果加载不成功,则抛出ClassNotFoundException异常。

双亲委派好处

  • 避免同一个类被多次加载;

    比如加载位于rt.jar包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个Object对象

  • 每个加载器只能加载自己范围内的类;

双亲委派模型的破坏者-线程上下文类加载器