首页
类加载
类的生命周期
加载 -> 连接(验证 -> 准备 -> 解析)-> 初始化 -> 使用 -> 卸载
类加载机制
类型的加载、连接和初始化过程都是在Java程序运行期间完成的,会给提起编译带来困难,也会增加性能开销,而动态加载和动态连接保证了Java语言可动态拓展的能力。
类加载的时机
有且只有六种主动引用的情况必须对类进行初始化(类加载自然要在初始化之前开始):
- 遇到new、getstatic、putstatic、invokestatic这四条指令时,典型的场景有:使用new关键字、读取或设置一个类型的静态字段、调用一个类型的静态方法;
- 对类型进行反射调用的时候;
- 初始化类时,如果其父类还没初始化,会触发其父类的初始化(接口并不需要);
- 虚拟机启动时,虚拟机会初始化主类(包含mian()方法的类);
- 动态语言支持,MethodHandle实例解析后的部分方法句柄需要进行初始化;
- 接口中定义了默认方法,如果接口的实现类发生了初始化,需要先初始化接口。
被动引用不会触发初始化:
- 通过子类引用父类的静态字段,不会导致子类初始化;
- 通过数组定义引用类,不会导致此类的初始化;
- 引用某个类的常量,不会导致此类的初始化(常量传播优化:编译期间会将其他类的常量值存储在调用类的常量池中)。
类加载的过程
加载
- 通过一个类的全限定名获取定义此类的二进制字节流;
- 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构;
- 在内存生成一个代表这个类的Class对象,作为方法区这个类的各个数据的访问入口。
连接
加载阶段与连接阶段的部分动作是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,确定的是连接阶段的开始固定晚于加载阶段的开始时间。
-
验证:确保类的字节流中包含的信息符合全部的约束要求,保证这些信息被当成代码运行后不会危害虚拟机自身的安全。
-
准备:为类变量(即静态变量)分配内存并设置系统要求的初始零值。JDK7之前是分配在永久代,而JDK8之后是随Class对象存在在堆中。
-
解析:将常量池内的符号引用替换为直接引用的过程。只要求解析阶段在执行操作符号引用的指令之前,虚拟机实现可以在还没有执行代码时就提前解析,同时可以对第一次解析的结果进行缓存。
- invokedynamic指令:必须等到程序执行到这条指令时才去进行解析;Lambda表达式和接口默认方法底层实现。
初始化
类加载过程的最后一步,执行类构造器<clinit>方法,只有主动引用才会触发类的初始化。
- <clinit>方法:
- 由Javac编译器自动生成,由类中所有类变量的赋值动作和静态语句块中的语句合并而成;
- 子类的<clinit>方法执行前虚拟机会保证其父类的<clinit>方法已经执行完毕;
- 并不是必须的,可以不生成;
- 接口不存在静态语句块但可以存在类变量的赋值操作,故也会生成<clinit>方法,但是接口的<clinit>方法执行不需要先执行父接口的<clinit>方法;
- 虚拟机会保证<clinit>方法在多线程环境中被正确的加锁同步,可能造成多个进程阻塞。
类加载器
负责“通过一个类的全限定名获取定义此类的二进制字节流”这一动作;每一个类加载器都有独立的类名称空间,任意一个类都必须由加载它的类加载器和这个类本身才能确定其在Java虚拟机中的唯一性。
- 数组的类对象不是由类加载器创建的,而是运行时由虚拟机创建的;
- 对象数组的类加载器与其元素的类加载器相同;
- 基本数据类型和基本数据类型数组没有类加载器。
双亲委派模型
从虚拟机的角度只存在启动类加载器(C++语言实现,属于虚拟机的一部分)和其他类加载器(Java语言实现,继承自java.lang.ClassLoader)。双亲委派模型要求除了启动类加载器,其余的类加载器都应有自己的父类加载器。
- 启动类加载器(Bootstrap Class Loader):负责加载lib目录下且能够被Java虚拟机识别的类库;无法被Java程序直接引用,如果需要将加载请求委派给启动类加载器处理需要使用null。
拓展类加载器(Extension Class Loader):由Java代码实现,属于Java系统类库的拓展机制,负责加载\lib\ext目录下的类库。- 平台类加载器(Platform Class Loader):JDK9引入模块化之后替代了拓展类加载器,用于加载拓展的系统类。
- 应用程序类加载器(Application Class Loader):负责加载用户路径上的所有类库;也称为系统类加载器,为ClassLoader.getSystemClassLoader()的放回值。
- 线程上下文类加载器(Thread Context Class Loader):通过java.lang.Thread类的setContextClassLoader()设置,默认返回应用程序类加载器。
双亲委派模型的工作过程
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载,而是把请求委派给父类加载器去完成,每一个层次的类加载器都是如此,只有当父类加载器无法完成加载请求时,子加载器才会尝试自己去完成加载。它是用来保证Java程序运行所需要的核心类被篡改和重复加载,JDK1.2中引入,在ClassLoader类的loadClass()方法中实现。
破坏双亲委派模型
- JDK1.2时才引入,为了向前兼容,loadClass方法修饰符为protected,且增加了一个新的protected方法findClass交由用户去重写;
- Java中SPI的加载,如JNDI、JDBC等,ServiceLoader使用线程上下文类加载器来加载用户的具体实现;
- Java模块化、热部署技术。
模块化
jdk.internal.loader.BuiltinClassLoader实现了JDK9中模块化架构下的类加载的逻辑,作为新增加的BootClassLoader、PlatformClassLoader、AppClassLoader的父类;三个类加载器各自负责加载不同的模块,平台及应用程序类加载器收到类加载请求后,会优先委派给该类归属模块的类加载器完成加载。