目录
1. 类加载过程
1.1 Class类文件加载阶段
- Loading(加载)
- Linking(连接) , Linkg又分为Verfication(验证)、Preparation(准备)、Resolution(解析)
- Initializing(初始化)
1.1.1 Linking过程
-
Verfication
验证阶段大致上会完成4个阶段的校验动作:文件格式校验、元数据校验、字节码校验、符号引用校验
以其中的文件格式校验为例,会校验进来的class文件是否符合class文件的标准,如果不是以魔数0xCAFEBABE开头,则在这一步骤就被拒绝了 -
Prepration
准备阶段是为类变量分配内存并设置类变量初始值的阶段,变量所使用的内存都将在方法区中进行分配。进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中举例
1.static int i = 8,注意在这里是把i值赋值为0,而不是8, 把i赋值为8动作是在初始化完成的
2. static final int value = 123,编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123 -
Resolution
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
1.1.2 Initializing
类初始化阶段是类加载过程的最后一步,到了初始化阶段,才真正开始执行类中定义的Java程序代码。
静态变量在这时候才成为初始值,初始化阶段是执行类构造器<clinit>()
方法的过程
-
<clinit>()
方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块
)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的 -
<clinit>()
方法与类的构造函数(或者说实例构造器<init>()
方法)不同,它不需要显示地调用父类构造器,虚拟机会保证在子类的<clinit>()
方法执行之前,父类的<clinit>()
方法已经执行完毕。因此在虚拟机中第一个被执行的<clinit>()
方法的类肯定是java.lang.Object -
由于父类的
<clinit>()
方法先执行,所以父类中的定义的静态语句块优先于子类的变量赋值操作public class Parent { public static int A = 1; static { A = 2; } } class Sub extends Parent{ public static int B = A; public static void main(String[] args) { System.out.println(Sub.B); // 结果为2 } }
2. 类加载器
2.1 类加载器介绍
定义 : 通过一个类的全限定名来获取描述此类的二进制字节流这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类,实现这个动作的代码模块称为"类加载器"
- Bootstrap ClassLoader(启动类加载器):加载放在
<JAVA_HOME>\lib
,加载jdk最核心的内容比如rt.jar、charset.jar等核心类,当调用getClassLoader()得到加载器的结果是一个空值的时候说明已经达到了最顶层的加载器 - Extension ClassLoader(扩展类加载器):由
sun.misc.Launcher$ExtClassLoader
实现,它负责加载<JAVA_HOME>\lib\ext
目录中,加载扩展包的各种各样文件 - Application ClassLoader(应用程序类加载器):由
sun.misc.Launcher$AppClassLoader
实现。用于加载classpath指定的内容 - 自定义ClassLoader:加载自定义的加载器
案例演示:
public class ClassLoaderLevel {
public static void main(String[] args) {
// 查看是谁load到内存的,执行结果为null,Bootstrap使用c++实现的,Java里并没有class和他对应
System.out.println(String.class.getClassLoader()); // null
// 核心类库某个包里的类,执行结果为null,被Bootstrap类加载器加载
System.out