【深入理解】Java的类加载过程
问题概述
类加载就是把.class文件的二进制数据读进内存,经过校验,转换、最终变成JVM能用的Class对象。
二进制流不一定非得来自.class文件,也可以是字节码工具动态生成的、或者从网络传过来的,只要格式对,JVM都认。
具体流程细节
整个类加载流程分为三大阶段:加载、连接、初始化。连接又能拆分成验证、准备、解析三步。所以细分下来是5个阶段:
1,加载
把二进制流读进内存,在方法区生成类的运行时数据结构,同时在堆里创建一个class对象作为访问入口。
2,验证
校验二进制流是否符合Class文件规范,包括魔数检查、版本号校验、元数据验证、字节码验证、符好引用验证。这一步是为了防止恶意代码搞崩JVM。
3,准备
给类变量(static修饰的变量)分配内存并设置初始零值。注意这里只是零值,比如static int a = 123在准备阶段a的值是0,不是123。但如果是static final int a = 123,编译期就确定了,准备阶段直接赋值123。
4,解析
把常量池里的符号引用替换成直接引用。符号引用就是一个字符串形式的标识,比如Java/lang/Object;直接引用是真正的内存地址或偏移量,能直接定位到目标。
5,初始化
执行类构造器<clinit>()方法,这时候才真正执行static int a = 123这种赋值操作,静态代码块就是在这个阶段跑的。
准备阶段和初始化的区别:
publicclassLoadingDemo{// 准备阶段:value = 0// 初始化阶段:value = 100privatestaticintvalue=100;// 准备阶段就直接赋值 200,因为是 final 常量privatestaticfinalintCONSTANT=200;static{System.out.println("静态代码块执行,value = "+value);}}扩展
1,类加载的触发时机
JVM规范规定了6种情况必须立即对类进行初始化:
- 遇到new、getstatic、putstatic、invokestatic这4条字节码指令时。对应的Java代码就是new对象、读取或设置类的静态字段(被final修饰的常量除外)、调用静态方法
- 使用Java.lang.reflect包对类进行反射调用时
- 初始化子类时发现父类还没初始化,先把父类初始化了
- JVM启动时指定的主类(包含main方法对应的那个类)
- JDK7开始的动态语言支持,如果MethodHandle实例解析结果是REF_getstatic、REF_putstatic、REF_invokestatic、REF_newInvokeSpecial这四种句柄,对应的类要先初始化
- 接口中定义了default方法,实现类初始化前要先初始化这个接口
2,类加载器的层次结构
JVM的类加载采用双亲委派模型,有三种内置的类加载器:
1,Bootstrap ClassLoader:最顶层的加载器,C++实现的,负责加载JAVA_HOME/lib目录下的核心类库。
2,Extension ClassLoader:负责加载JAVA_HOME/lib/ext目录下的扩展类库。JDK9之后改名叫Platform ClassLoader
3,Application ClassLoader:加载classpath下的类,也就是我们自己写的代码和引入的第三方jar包