类加载
类文件结构:
根据JVM规范,类文件结构如下:
- 魔数:
0-3字节,表示它是否是【class】类型的文件
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09
- 版本:
4-7字节,表示类的版本00 34 (52)(16进制)表示是Java 8
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09
常量池:
- 8~9字节,表示常量池长度,00 23 (35)表示常量池有#l-#34项,注意#0项不计入,也没有值
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09
- 第#1项0a表示一个Method信息,00 06和00 15(21)表示它引用了常量池中#6和#21项来获得这个方法的【所属类】和【方法名】
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09
- 。。。
类加载阶段--加载:
将类的字节码载入方法区中,内部采用C++的instanceKlass描述java类,它的重要field有:
- _java_mirror即java的类镜像,例如对string.来说,就是String.class,作用是把klass暴露给java使用
- _super即父类
- _fields即成员变量
- _methods即方法
- _constants即常量池
- _class_loader即类加载器
- _vtable虚方法表
- _itable接口方法表
如果这个类还有父类没有加载,先加载父类
加载和链接可能是交替运行的
注意:
- instanceKlass这样的【元数据】是存储在方法区(1.8后的元空间内),但_java_mirror是存储在堆中
- 可以通过前面介绍的HSDB工具查看
类加载阶段--链接:
- 验证:验证类是否符合JVM规范,安全性检查;用UE等支持二进制的编辑器修改HelloWorld.class的魔数,在控制台运行
- 准备:为static变量分配空间,设置默认值
- static变量在JDK7之前存储于instanceKlass末尾,从JDK7开始,存储于_java_mirror末尾
- static变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成
- 如果static变量是final的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
- 如果static变量是final的,但属于引用类型,那么赋值也会在初始化阶段完成
- 将常量池中的符号引用解析为直接引用;经过解析之后,可以确切的知道类、方法等东西在内存中的位置;
类加载阶段--初始化:
发生的时机:
概括得说,类初始化是【懒惰的】
- main方法所在的类,总会被首先初始化
- 首次访问这个类的静态变量或静态方法时
- 子类初始化,如果父类还没初始化,会引发
- 子类访问父类的静态变量,只会触发父类的初始化
- Class.forName
- new会导致初始化
不会导致类初始化的情况
- 访问类的static final静态常量(基本类型和字符串)不会触发初始化
- 类对象.class不会触发初始化
- 创建该类的数组不会触发初始化
- 类加载器的loadClass方法
- Class.forName的参数2为false时