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) 表示 Java8 【53表示Java9】 34是16进制转成十进制是52
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09
8-9字节,表示常量池长度,00 23(35)表示常量池有#1 ~ #34项,注意#0项不计入,也没有值
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09
举例:
常量池入口固定需要放置一项u2类型的数据表示常量池项数,也就是两个字节长度,也就是0x0016表示常量池有22项,因为是16进制所以需要转换。
第一项开头是07,找上方的表,对应是
所以找到Class_info的常量的结构
tag占u1也就是一个字节,也就是刚才的07
name_index是常量池的索引值,它指向常量池中一个 CONSTANT_Utf8_info类型常量,此常量代表了这个类(或者接口)的全限定名
name_index占u2也就是两个字节,所以后面两位字节表示的是需要找的项数也就是找第二项(0x02)
第二项开头是01,找上方的表,对应是
所以找到Utf8常量的结构
tag占u1也就是一个字节,也就是刚才的01
length占u2也就是两个字节,也就是接下来的两个字节00 1D转成十进制29
最后bytes占u1也就是一个字节,但是有length个,所以接下来的29个字节都是表示我们要找的东西。【org/fenixsoft/clazz/TestClass】
00 21 需要使用标志位0x0001 | 0x0020 也就是一个public类型 ...不是一个接口
查表得知
对于接口索引集合,入口的第一项u2类型的数据为接口计数器(interfaces_count),表示索引表 的容量。如果该类没有实现任何接口,则该计数器值为0,后面接口的索引表不再占用任何字节。
从偏移地址0x000000F1开始的3个u2类型的值分别为0x0001、0x0003、0x0000,也就是类索引为 1,父类索引为3,接口索引集合大小为0。
字段表(field_info)用于描述接口或者类中声明的变量。Java语言中的“字段”(Field)包括类级变 量以及实例级变量,但不包括在方法内部声明的局部变量。
描述符的作用是用来描述字段 的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类 型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大 写字符来表示,而对象类型则用字符L加对象的全限定名来表示,
字段表集合从地址0x000000F8开始,第一个u2 类型的数据为容量计数器fields_count,如图6-8所示,其值为0x0001,说明这个类只有一个字段表数 据。接下来紧跟着容量计数器的是access_flags标志,值为0x0002,代表private修饰符的ACC_PRIVATE 标志位为真(ACC_PRIVATE标志的值为0x0002),其他修饰符为假。代表字段名称的name_index的值 为0x0005,从代码清单6-2列出的常量表中可查得第五项常量是一个CONSTANT_Utf8_info类型的字符 串,其值为“m”,代表字段描述符的descriptor_index的值为0x0006,指向常量池的字符串“I”。根据这些 信息,我们可以推断出原代码定义的字段为“private int m;”。
方法的定义可以通过访问标志、名称索引、描述符索引来 表达清楚,但方法里面的代码去哪里了?方法里的Java代码,经过Javac编译器编译成字节码指令之 后,存放在方法属性表集合中一个名为“Code”的属性里面,可以看《深入理解Java虚拟机》这本书的6.3.7部分,后面再补充。
方法表集合的 入口地址为0x00000101,第一个u2类型的数据(即计数器容量)的值为0x0002,代表集合中有两个方 法,这两个方法为编译器添加的实例构造器和源码中定义的方法inc()。第一个方法的访问标志值 为0x0001,也就是只有ACC_PUBLIC标志为真,名称索引值为0x0007,查代码清单6-2的常量池得方法 名为“”,描述符索引值为0x0008,对应常量为“()V”,属性表计数器attributes_count的值为 0x0001,表示此方法的属性表集合有1项属性,属性名称的索引值为0x0009,对应常量为“Code”,说明 此属性是方法的字节码描述。
编译器会按照从上到下的顺序,收集所有静态代码块和静态成员赋值的代码,合并成为一个特殊的方法<cinit>()V:
<cinit>()V 方法会在类加载的初始化阶段被调用
所以运行结果是30
编译器会按照从上到下的顺序,将代码块{} 和 成员变量赋值的代码,组合成一个新的构成方法,但是原始构造方法会最后执行。
所以运行结果是 s3 30
如果是静态方法的调用,如果通过对象类调用,虚拟机字节码还是会执行类名调用的方式。
invokespecial 和 invokestatic属于静态绑定,字节码生成的时候就已经知道这两类的方法,可以唯一确定的找到,当时invokevirtual也就是普通方法,有可能会被子类重写,所以无法唯一确定,需要多次查找,才能确定程序入口。
当执行invokevirtual指令时,
总结:
题目:
结果: 20
总结:
题目:
答案:10
通过这个例子可以发现
总结:
java文件编译成class文件过程中,自动生成和转换的一些代码,减轻程序员负担。
总结:
字节码中方法体里面的泛型信息就被擦除了,但是不是所有都擦除,局部变量泛型信息可以找到
JDK5以后加入的新特性
如果调用foo(),没有传入值,会创建一个空的数组,而不会传递null进去
注意
总结:
1、为什么需要比较完hashCode码之后还要比较一遍字符串值
2、为什么不直接进行比较字符串值,省去比较hashCode的操作
例:
BM 和 C. 这两个字符串的hashCode值都是2123
重写规则,子类方法的返回类型不能超过父类方法的返回类型,子类方法的访问修饰符不能小于父类的访问修饰符
匿名内部类引用了外部的一个final修饰的变量,固定语法
为什么需要是final修饰呢?
因为如果x的值不是final修饰的,那么说明是可以改变的,但是我们初始化的时候已经是传进去了一开始的值,内部类记录了已经是原先的值,你这时候修改了,跟内部类里面的值就不相同了。