类的加载过程
类的加载过程分为三个阶段:加载 链接 初始化
一、加载
Java 虚拟机一般使用Java 类的流程:首先将开发者编写的Java 源代码(.java 文件)编译生成Java 字节码文件(.class 文件),然后类加载器会读取字节码文件,并转换成java.lang.Class 对象。有了该 Class 对象后,Java 虚拟机可以利用反射方法创建其真正的对象了。Java 提供的类加载器绝大多数都继承自 ClassLoader 类,它们被用来加载不 同来源的字节码文件。
提到类的加载,就要说类的加载器。系统提供三种类加载器,分别是:
- Bootstrap ClassLoader(启动类加载器又称为根加载器/引导类加载器)
- ExtClassLoader(扩展类加载器)
- AppClassLoader(应用类加载器)
每一种类加载器都有其指定的类加载路径。
- Bootstrap ClassLoader 主要加载 JAVA_HOME/jre/lib 里的 jar 包,该目录下的所有 jar 包都是运行 JVM 时所必需的 jar 包。注意:类加载器其实自身也是一个 Java 类,因此,自身类加载器需要被其他类加载器进行加载后方可使用,显然必须有一个类加载器的顶级父类(也就是 Bootstrap ClassLoader,该类加载器是由 C 语言代码进行开发的)是其他类加载器的父类。关键点在于,如果一个类的类加载器是 Bootstrap ClassLoader,那么该类的 getClassLoader()方法返回 null。
- ExtClassLoader 主要加载 Java 核心扩展类,即 JAVA_HOME/jre/ext 目录下的 jar 文件。
- AppClassLoader 主要加载的是开发者在应用程序中编写的类,即 CLASSPATH 路径下所有的 jar 文件。
三种加载器之间的关系:
这里我们应该明确一个原则:一个类只会生成一个class对象,无论它被new了几次。 为什么这样说呢?这里就谈到了类加载器之间的双亲委派模型。
双亲委派模型的工作过程如下:、
- 当前类加载器从自己已经加载的类中查询是否此类已经加载,如果已经加载则返回原来已经加载的类。
- 如果没有找到,就去委托父类加载器去加载。父类加载器也会采用同样的策略,查看自己已经加载过的 类中是否包含这个类,有就返回,没有就委托其父类去加载,直到委托到启动类加载器为止。因为如果父类加载器为空了,就代表使用启动类加载器作为父加载器去加载该类。
- 如果启动类加载器加载失败,就会使用扩展类加载器来尝试加载,继续失败则会使用 AppClassLoader 来加载,继续失败就会抛出一个异常 ClassNotFoundException
使用双亲委派模型的好处:
- 安全性,避免用户自己编写的类动态替换 Java 的一些核心类。如果不采用双亲委派模型的加载方式进行 类的加载工作,那我们就可以随时使用自定义的类来动态替代 Java 核心 API 中定义的类。例如:如果黑客将“病毒 代码”植入到自定义的 String 类当中,随后类加载器将自定义的 String 类加载到 JVM 上,那么此时就会对 JVM 产生意想不到“病毒攻击”。而双亲委派的这种加载方式就可以避免这种情况,因为 String 类已经在启动时就被引导类加载器进行了加载。
- 避免类的重复加载,因为 JVM 判定两个类是否是同一个类,不仅仅根据类名是否相同进行判定,还需要 判断加载该类的类加载器是否是同一个类加载器,相同的 class 文件被不同的类加载器加载得到的结果就是两个不同的类。
二、链接
当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接又可分为如下3个阶段:
- 验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。Java是相对C++语言是安全的语言,例如它有C++不具有的数组越界的检查。这本身就是对自身安全的一种保护。验证阶段是Java非常重要的一个阶段,它会直接的保证应用是否会被恶意入侵的一道重要的防线,越是严谨的验证机制越安全。验证的目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。其主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
- 准备:类准备阶段负责为类的静态变量分配内存,并设置默认初始值。
- 解析:将类的二进制数据中的符号引用替换成直接引用。说明一下:符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。
三、初始化
初始化是为类的静态变量赋予正确的初始值,如果类中有语句:private static int a = 10,它的执行过程是:首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析(后面在说),到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。
本文参考亦心_yan的博客,博客链接:https://blog.csdn.net/m0_38075425/article/details/81627349