一、类加载简介
类的加载机制是指把编译后的.class类文件的二进制数据读取到内存中,并为之创建一个java.lang.Class对象,用来封装类在元数据空间的数据结构。
类在JVM中的生命周期为:加载,连接,初始化,使用,卸载。不过这里只重点描述加载,连接,初始化这三个过程。
二、加载过程
基于一张图看类加载子系统的细节流程:
1、加载阶段
过程描述
加载阶段需要完成以下三个过程:
- 通过类的全限定名来获取其定义的二进制字节流;
- 将字节流所代表的静态存储结构转化为云数据空间的运行时数据结构;
- 在堆Heap中生成一个代表这个类的java.lang.Class对象,作为对元数据空间中这些数据的访问入口;
类加载器
- 引导类加载器
Bootstrap-ClassLoader基于C/C++实现,负责加载Java的核心类库JAVA_HOME\jre\lib\rt.jar,该加载器不继承自ClassLoader抽象类,并且只加载包名为java、javax、sun等开头类,一次保证对核心源码的保护。
- 扩展类加载器
Extension-ClassLoader,基于Java语言,由sun.misc.Launcher$ExtClassLoader实现,派生于ClassLoader抽象类,从java.ext.dirs系统变量指定的路径中的加载类库,或者JDK安装目录jre\lib\ext目录下加载。
- 系统类加载器
Application-ClassLoader,基于Java语言,由sun.misc.Launcher$ExtClassLoader实现,它负责加载环境变量ClassPath指定的类库,如果在应用程序中没有自定义类加载器,一般情况下作为程序中默认的类加载器。
2、连接阶段
验证
目的在于确保Class文件的字节流中包含的信息符合当前虚拟机的要求,保证加载类的正确性,不会危害虚拟机自身的安全,主要包括四种检验动作:
- 文件格式验证:验证字节流是否符合Class文件格式的规范;
- 元数据验证:确保其描述的信息符合Java语言规范的要求;
- 字节码验证:确定程序语义是符合逻辑的;
- 符号引用验证:确保解析动作能正确执行。
准备
为类的静态变量分配内存,并初始化为默认值,这时候进行内存分配的仅包括类变量(static)修饰,不包括(final-static)修饰的,这里也不会为实例变量分配初始化,实例变量会随着对象一块分配到Java堆中。
解析
将常量池中的符号引用转换为直接引用的过程,直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。解析主要针对类或接口、字段、类方法、接口方法、方法类型等,解析的动作实际是会随着JVM在执行完初始化之后再执行的。
3、初始化阶段
执行类构造器clinit()方法的过程,该方法不需要自定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来,Jvm要保证clinit()方法在多线程访问下的安全性。
三、机制策略
1、双亲委派模式
类加载器收到了类加载的请求时,不会自己先去尝试加载这个类,而是把请求委托给父加载器去执行;
如果父加载器还存在父类加载器,则依次向上委托,因此类加载请求最终都应该被传递到顶层的启动类加载器中;
如果父类加载器可以完成类加载请求,就直接成功返回,只有当父加载器在无法完成该加载,子加载器才会尝试自己去加载该类;
2、沙箱安全机制
假设自定义一个类名为String且所在包为java.lang,在使用引导类加载器加载时会先加载JDK中的String类,因为这个类本来是属于jdk的,后面再次出现String类就会报错,以此保证源代码不被恶意篡改,这就是沙箱安全机制。