字节码是编译后的文件,格式固定,对一些个人认为关键的属性做一些总结:
1、两个类在编译成Class之后就不存在任何联系了。如果只修改一个字节码文件,对运行时可能会有影响,最好重新进行编译。
2、指向常量池的索引值为0的数据,说明它不引用任何常量池项。
3、指令码+操作数,操作数来源于操作数栈或者局部变量表,操作数栈存储栈帧计算的中间结果。32位的字节,由虚拟机内部实现。
4、重载本质:编译器通过参数的静态类型决定重载版本,编译期决定。
重写本质:invokevirtual指令把常量池里类方法的符号引用解析到了不同的直接引用上。
5、如果当前字节码PC计数器的值已经超出了某个变量的作用域,那这个变量对应的Slot就可以交给其他变量使用。
6、字段表集合中不会列出从超类或者父接口中继承而来的字段,但是内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
7、方法表集合中不会出现来自父类的方法信息,重写(Override)除外。
8、接口或者抽象类中的方法就不存在Code属性。
9、用描述符来描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号“()”之内。
10、代码级方法特征签名包括:方法名称、参数顺序及参数类型,字节码级别方法签名还包括了返回值以及受查异常表。
11、max_stack:操作数栈深度的最大值。在方法执行的任意时刻,操作数栈都不会超过这个深度。JVM运行时需要根据这个值来分配栈帧(Stack Frame)中的操作栈深度。
12、Exceptions属性是在方法表中与Code属性平级的一项属性,列举出方法中可能抛出的受查异常(Checked Excepitons),也就是方法描述时在throws关键字后面列举的异常。
13、局部变量在字节码的作用域范围:生命周期开始的字节码偏移量(start_pc)、范围覆盖的长度(length),index是这个局部变量在栈帧局部变量表中Slot的位置。
14、StackMapTable属性中包含零至多个栈映射帧(Stack Map Frames),每个栈映射帧都显式或隐式地代表了一个字节码偏移量,用于表示该执行到该字节码时局部变量表和操作数栈的验证类型。类型检查验证器会通过检查目标方法的局部变量和操作数栈所需要的类型来确定一段字节码指令是否符合逻辑约束。
15、Signature:存储一个方法在字节码层面的特征签名,这个属性中保存的参数类型并不是原生类型,而是包括了参数化类型的信息。擦除法所谓的擦除,仅仅是对方法的Code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射手段取得参数化类型的根本依据。
16、方法调用:
1.invokespecial:调用构造器、私有方法和父类方法;
2.invokestatic:调用静态方法;
3.invokevirtual:调用虚方法,一般的实例方法都是invokevirtual调用;
4.invokeinterface:调用接口类的方法;
5.invokedynamic,java中对动态语言的支持。
6.invokevirtual和invokeinterface通过第一个参数查找方法,动态分派,从而实现多态。
17、数组:
数组类的可见性与它的组件类型的可见性一致,如果组件类型不是引用类型,那数组类的可见性将默认为public。
Java语言中对数组的访问比C/C++相对安全是因为这个类封装了数组元素的访问方法,而C/C++直接翻译为对数组指针的移动。
越界检查不是封装在数组元素访问的类中,而是封装在数组访问的xaload、xastore字节码指令中。
数组类的元素类型(Element Type,指的是数组去掉所有维度的类型)最终是要靠类加载器去创建
18、局部变量表与操作数栈,两者的大小都是在编译时确定的,因此,一个方法的栈帧的大小也是固定不变的。
局部变量表数组索引从0开始,所表示的变量是this,即是当前实例的引用。局部变量表中的Slot是否还存有关于对象的引用,否则被回收。
每个方法都在操作数栈和局部变量数组之间交换数据,并且压入或者弹出其他方法返回的结果,局部变量数组和操作数栈之间的数据传输是使用通过大量的load指令(aload,iload)和store指令(astore,istore)来实现的,如aload_0: 把局部变量数组中索引为#0的变量添加到操作数栈上。