Class Loader
类加载器用于将 class 文件装载进系统,交由 JVM 进行链接、初始化等工作。
CLassLoader 有以下 4 种:
-
启动类加载器(Bootstrap ClassLoader): 负责加载 JAVA_HOME\lib 目录中的且被虚拟机认可(按文件名识别,如 rt.jar)的类
-
扩展类加载器(Extension ClassLoader): 负责加载 JAVA_HOME\lib\ext 目录中的类库
-
应用程序类加载器(Application ClassLoader):. 负责加载用户路径(classpath)上的类库
-
User ClassLoader: 用户自定义类加载器
双亲委派机制
1、自底向上查找类是否已经加载
2、自顶向下尝试加载类
作用:避免多份同样的字节码文件被重复加载。 例如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object 对象。
类的加载过程
JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化
加载
类加载器将 class 文件加载进内存,这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的入口。
验证
确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
准备
准备阶段是正式为类变量分配内存空间并为类变量设置初始值的阶段。常量不一样,会直接赋值。
解析
虚拟机将常量池中的符号引用替换为直接引用的过程。
简单来说符号引用就是 class 文件中的字面量。直接引用就是内存中对象的地址。
初始化
初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。
类构造器 <clinit>
初始化阶段是执行类构造器 <clinit> 方法的过程。<clinit> 方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证子 <clinit> 方法执行之前,父类的 <clinit> 方法已经执行完毕,如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成 <clinit> 方法。
因此类实例化的一般过程是:父类的类构造器<clinit>() → 子类的类构造器<clinit>() → 父类的成员变量和实例代码块 → 父类的构造函数 → 子类的成员变量和实例代码块 → 子类的构造函数。
类的主动引用(一定会发生初始化)
-
new 一个类的对象
-
调用类的静态成员(除了 final 常量)和静态方法
-
使用 java.lang.reflect 包的方法对类进行反射调用
-
当虚拟机启动,说白了就是 main 方法所在的类
-
当初始化一个类,如果其父类没有被初始化,则会先初始化它的父类
类的被动引用(不会发生初始化)
-
通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
-
定义对象数组,不会触发该类的初始化。
-
常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。
-
通过类名获取 Class 对象,不会触发类的初始化。
-
通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。
-
通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作。
loadClass 与 forName 的区别
Classloader.loadClass 得到的 class 是还没有链接的
Class.forName 得到的 class 是已经初始化完成的