类的生命周期:
类加载过程
系统加载 Class 类型的文件主要三步:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析。
1、加载
- 通过全类名获取定义此类的二进制字节流
- 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
- 在内存中生成一个代表该类的
Class
对象,作为方法区这些数据的访问入口
数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。
加载阶段和连接阶段的部分内容是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始。
2、验证
对文件格式、元数据、字节码、符号引用进行验证。
- 验证字节流是否符合Class文件格式的规范。
- 对字节码描述的信息进行语义分析,验证这些信息是否符合Java语言的规范。
- 通过数据流和控制流进行分析,验证程序语义是否合法、是否符合规范。
- 确保解析动作能正确执行。
3、准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。
这时候进行内存分配的仅包括类变量,而不包括实例变量。实例变量会在对象实例化时随着对象一块分配在 Java 堆中。
类变量所使用的内存都应当在 方法区 中进行分配,在 JDK 7 及之后,HotSpot 已经把原本放在永久代的字符串常量池、静态变量等移动到堆中,这个时候类变量则会随着 Class 对象一起存放在 Java 堆中。
(这里所设置的初始值"通常情况"下是数据类型默认的零值(如 0、0L、null、false 等),比如我们定义了public static int value=111
,那么 value 变量在准备阶段的初始值就是 0 而不是 111(初始化阶段才会赋值)。特殊情况:比如给 value 变量加上了 final 关键字public static final int value=111
,那么准备阶段 value 的值就被赋值为 111。)
4、解析
虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
5、初始化
初始化阶段是执行初始化方法 <clinit> ()
方法的过程
类加载器:
所有的类都由类加载器加载,加载的作用就是将 .class
文件加载到内存。
数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。
JVM 中内置了三个重要的 ClassLoader,分别是BootstrapClassLoader(启动类加载器)
ExtensionClassLoader(扩展类加载器)、AppClassLoader(应用程序类加载器)
除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader
:
双亲委派模型
每一个类都有一个对应它的类加载器。 ClassLoader 在协同工作的时候会默认使用 双亲委派模型 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派给父类加载器的 loadClass()
处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader
中。当父类加载器无法处理时,才由自己来处理。当父类加载器为 null 时,会使用启动类加载器 BootstrapClassLoader
作为父类加载器。
双亲委派模型保证了 Java 程序的稳定运行,可以避免类的重复加载,也保证了 Java 的核心 API 不被篡改。