jvm-009
一、类的加载
类加载的最终产品是位于内存中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序提供了访问方法区内该类的数据结构的接口。
类加载器并不需要等到某个类被首次主动使用时才去加载它,而是预料到某个类将要被使用时就预先加载它。如果在加载的过程中遇到了.class文件缺失或者文件错误,类加载器必须在首次主动使用该类时才报告错误(LinkageError错误),如果该类一直没有被程序主动使用,那么类加载器就不会报告错误。
二、类的连接
类被加载后,就进入到连接阶段。连接就是将已经读入到内存中的类的二进制数据合并到虚拟机的运行时环境中去。
1、验证:
A:文件格式的验证:基于二进制字节流进行的验证,只有通过这个阶段的验证之后,字节流才会进入方法去进行存储。
□ 是否是CAFEBABE魔数开头;
□ 虚拟机的主次版本号是否在当前版本的虚拟机处理范围之内;
□ 常量池中是否有不被支持的常量类型(tag检查);
□ 指向常量池的索引值中是否有不存在或者不符合类型的常量;
。。。
B:元数据的验证:主要目的是对类的元数据进行语义校验。
□ 是否包含父类(除了java.lang.Object之外,均应包含父类);
□ 是否继承了final修饰的类;
□ 是否实现了其父类或者接口中要求实现的所有方法;
□ 类中的字段或方法是否与父类中的字段方法矛盾,比如不符合规则的重载,覆盖了父类中final的字段;
。。。
C:字节码的验证:对方法体进行的校验,是整个验证阶段最为复杂的。
□ 保证任意时刻的数据类型都能与字节码指令配合工作(例如操作数栈顶放置的是int型数据,但是却使用其他类型的字节码指令来操作它);
□ 字节码跳转指令不会指向方法以外的字节码指令上;
□ 保证方法中的类型转换是有效的(父类转子类,或者强转为与本身不相关的任何类型都是危险和不合法的);
。。。
D:符号解析的验证:在解析阶段发生,在虚拟机将符号引用转变为直接引用的时候进行校验。
□ 符号引用中通过字符串的全限定名描述符能否能找到对应的类型;
□ 在指定的类中,是否存在符合方法和字段的名称和描述符;
□ 所引用的字段和方法的修饰符是否允许可以被当前类所访问等。
。。。
2、准备:
为类变量分配内存并设置初始值的阶段,这些变量所使用的内存都在方法区中分配。通常情况下,赋值为零值,如:0,0L,false,null 等。
如果字段属性表中存在ConstangValue属性,即常量,会被初始化为正确的数值。
3、解析:虚拟机将常量池中的符号引用转变为直接引用的过程。
□ 符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到所引用的目标即可。
□ 直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄等。
三、类的初始化
1、初始化步骤:假如类还没有被加载,那就先加载和连接;
假如该类有父类且未被初始化,那就先初始化这个类的父类;
假如类中存在初始化语句,那就自上而下一次执行这些初始化语句。
2、初始化时机:
□ 只有当程序访问的静态变量或方法,是在当前接口或者类中定义时,才算是对类或者接口的主动使用,通过子类调用父类的字段,不算是对子类的主动使用。
□ 调用ClassLoader类的loadClass方法会加载一个类,但并不是对类的主动使用,不会导致类的初始化。
3、初始化的两种途径:
□ 在类中声明的静态属性
□ 在类的静态代码块中的代码
4、接口的初始化: 一个父接口的并不会因为它的子接口或者实现类的初始化而初始化,只有当程序主动使用接口中定义的变量时,才会引起接口的初始化。
□ 在初始化一个类时,并不会要求实现的接口的初始化。
□ 在初始化一个接口时,并不要求其父接口的初始化。
□ 接口中的方法默认是 public final
□ 接口中的字段默认是 public static final(常量)