《深入理解Java虚拟机》读书笔记——类文件结构、虚拟机类加载机制等

时间:2022-12-27 20:58:53

1、类文件结构,虚拟机接受Class字节码文件,是与操作系统和机器指令集无关的、平台中立的格式,其他语言也可以编译成Class文件,字节码命令所能提供的语义描述能力比Java语言更加强大。Class文件是一组以8位字节为基础单位的二进制流,只有无符号数和表两种数据类型,有如下一些组成部分:

    (1)魔数与Class文件版本:魔数唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件,0xCAFEBABE;

    (2)常量池:存放字面量和符号引用,字面量如文本字符串、声明为final的常量值等;符号引用包括类和接口的全限定名、字段的名称和描述符(数据类型)、方法的名称和描述符(参数列表和返回值)Class文件中不会保存各个方法和字段的内存信息,虚拟机运行时,从常量池获得符号引用,在类创建或运行时解析,得到具体的内存地址;  

    (3)访问标志:这个Class文件是类还是接口,是否为public、abstract、final等;

    (4)类索引、父类索引和接口索引集合:类的全限定名、父类的全限定名、实现了哪些接口;

    (5)字段表集合:类变量和实例变量(不包括局部变量)的修饰符、字段名称和描述符(数据类型)的地址,引用常量池中的符号引用、属性表集合;

    (6)方法表集合:访问标志、方法名称和描述符(参数列表和返回值)地址、属性表集合,方法里的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中的“Code”属性里

    (7)属性表集合:有Code、Exceptions、LocalVariableTable、ConstantValue(通知虚拟机自动为static变量赋值)、Signature(记录泛型信息)等很多属性;


2、字节码指令

    (1)大部分指令都没有支持byte、char、short,没有任何指令支持boolean,在编译器或运行期会扩展为int;

    (2)没有直接支持byte、short、char和boolean的算术指令;

    (3)窄化类型转换要用显示转换指令;

    (4)同步指令用管程(Monitor)来支持,同步方法会设置方法表集合中的ACC_SYNCHRONIZED访问标志,如果设置了,执行线程要先持有管程;synchronized块使用虚拟机指令集中的monitorenter和monitorexit指令


3、虚拟机类加载机制

    类的生命周期包括加载、验证、准备、解析、初始化、使用和卸载七个阶段,验证、准备和解析阶段统称为连接,解析阶段可能在初始化之后(动态绑定);

    何时初始化:new、使用类静态字段(static final的,已在编译期放入常量池的除外)、使用类静态方法、反射调用、父类未初始化(是类不是接口)则先触发父类的初始化、执行main()方法的类;

    有一些需要注意的情况:【1】子类引用父类的静态字段,子类不会初始化【2】定义类的数组不会初始化【3】使用进入常量池的静态变量不会初始化【4】接口初始化,不要求父接口初始化,实际使用父接口时才初始化

    (1)加载:通过一个类的全限定名来获取定义此类的二进制字节流(不一定是Class文件,还可以是ZIP包、网络、运行时计算生成的,即动态代理、数据库等),将字节流转化为方法区的运行时数据结构,在内存中生成一个Class对象,作为类的访问入口;

    (2)验证:文件格式、元数据(语义)、字节码、符号引用验证等;

    (3)准备:为类变量分配内存,设置初始值,仅包括static变量,不包括实例变量,赋零值,即0、false等。如果是static final的在字段属性表中存在ConstantValue属性,会赋给定的值

    (4)解析:将常量池内的符号引用替换为直接引用,即定位方法的地址,可能在初始化阶段之后执行;

    (5)初始化:执行类构造器<clinit>()方法,包括static变量的赋值和静态语句块。定义在static语句块之后的static变量,static语句块可以赋值,但不能访问。父类的<clinit>()方法先执行,执行接口的<clinit>()方法无需执行父接口的<clinit>()方法;


4、类加载器

    两个类是否相等首先比较类加载器是否相等

    可以分为两类:启动类加载器,用C++实现,是虚拟机一部分;另一种继承自抽象类java.lang.ClassLoader,用Java实现,独立于虚拟机外部;

    细致地可以分为启动类加载器(需要把请求委派给它使用null)、扩展类加载器、应用程序类加载器;

    双亲委派模型:类加载器收到类加载请求,委派给父类加载器去完成,父类无法加载才由子类加载,最终由启动类加载器去加载,确保类的一致性;

《深入理解Java虚拟机》读书笔记——类文件结构、虚拟机类加载机制等

    破坏双亲委派模型:即父类加载器请求子类加载器去加载,如OSGi,Java模块化的标准,类加载器成网状,每个模块连同类加载器一起替换;


5、栈帧

    (1)局部变量表:以slot为单位(32位),每个long、double分配两个slot,由于是线程私有,没有高低位不一致的问题。非线程安全的场合会高低位不一致,可以用64位JVM;

    局部变量表的第0个slot是this,没有准备阶段,要给局部变量赋值;

    (2)操作数栈:执行iadd等字节码指令;

    (3)方法返回地址;

    (4)动态连接:类加载阶段的解析是静态解析,另一部分在运行时转化为直接应用。一切方法在Class文件里都是符号引用,在类加载或运行时确定直接引用;


6、分派

    (1)重载,静态分派:Human man = new Man(),Human是变量的静态类型,编译期可知;Man是实际类型,运行时确定。重载根据参数的静态类型选择执行的方法,例如执行sayHello(man)会执行sayHello(Human guy);

    (2)重写,动态分派:运行期根据变量的实际类型选择执行的方法,例如man.sayHello()会执行Man类的sayHello();


7、编译期优化

    (1)泛型与类型擦除:编译时擦除类型,强转,在Signature中保留泛型信息,如下代码不能被编译;

public static void method(List<String> list){}
public static void method(List<Integer> list){}
    (2)包装类的“==”只有在遇到算术运算和数值时才自动拆箱;