Java编译后生成clas文件,想查看class16进制文件可以通过IEDA安装HexView插件或者使用Hex Editor工具查看。文件内容如图:
calss文件
class文件组成包括magic(魔数),minor_version(副版本号),major_version(正版本号),constant_pool_count(常量池计数器),access_flags(访问标记),this_class(类索引),
super_class(父类索引),interfaces_count(接口数量),interfaces(接口数组),fields(字段表),methods_count(方法数量),methods(方法表),attributes_count(属性数量),
attributes(属性表);
魔数
所有的由Java编译器编译⽽成的class⽂件的前4个字节都是“0xCAFEBABE”。它的作⽤在于:当JVM在尝试加载某个⽂件到内存中来的时候,会⾸先判断此class⽂件有没有JVM认为可以接受的“签名”,即JVM会⾸先读取⽂件的前4个字节,判断该4个字节是否是“0xCAFEBABE”,如果是,则JVM会认为可以将此⽂件当作class⽂件来加载并使⽤。
版本号
随着Java本身的发展,Java语⾔特性和JVM虚拟机也会有相应的更新和增强。⽬前我们能够⽤到的JDK版本如:1.5,1.6,1.7,还有现如今的1.8及更⾼的版本。发布新版本的⽬的在于:在原有的版本上增加新特性和相应的JVM虚拟机的优化。⽽随着主版本发布的次版本,则是修改相应主版本上出现的bug。我们平时只需要关注主版本就可以了。主版本号和次版本号在class⽂件中各占两个字节,副版本号占⽤第5、6两个字节,⽽主版本号则占⽤第7,8两个字节。JDK1.0的主版本号为45,以后的每个新主版本都会在原先版本的基础上加1。若现在使⽤的是JDK1.7编译出来的class⽂件,则相应的主版本号应该是51,对应的7,8个字节的⼗六进制的值应该是 0x33。
⼀个 JVM实例只能⽀持特定范围内的主版本号 (Mi ⾄Mj) 和 0 ⾄特定范围内 (0 ⾄ m) 的副版本号。假设⼀个 Class ⽂件的格式版本号为 V, 仅当Mi.0 ≤ v ≤ Mj.m成⽴时,这个 Class ⽂件才可以被此Java 虚拟机⽀持。不同版本的 Java 虚拟机实现⽀持的版本号也不同,⾼版本号的 Java 虚拟机实现可以⽀持低版本号的 Class ⽂件,反之则不成⽴。JVM在加载class⽂件的时候,会读取出主版本号,然后⽐较这个class⽂件的主版本号和JVM本身的版本号,如果JVM本身的版本号 < class⽂件的版本号,JVM会认为加载不了这个class⽂件,会抛出我们经常⻅到的" java.lang.UnsupportedClassVersionError: Bad version number in .class file "Error 错误;反之,JVM会认为可以加载此class⽂件,继续加载此class⽂件。
常量池计数器
常量池是class⽂件中⾮常重要的结构,它描述着整个class⽂件的字⾯量信息。常量池是由⼀组constant_pool结构体数组组成的,⽽数组的⼤⼩则由常量池计数器指定。常量池计数器constant_pool_count 的值 =constant_pool表中的成员数+ 1。constant_pool表的索引值只有在⼤于 0且⼩于constant_pool_count时才会被认为是有效的。
注意事项:
常量池计数器默认从1开始⽽不是从0开始:当constant_pool_count = 1时,常量池中的cp_info个数为0;当constant_pool_count为n时,常量池中的cp_info个数为n-1。
原因:
在指定class⽂件规范的时候,将索引#0项常量空出来是有特殊考虑的,这样当:某些数据在特定的情况下想表达“不引⽤任何⼀个常量池项”的意思时,就可以将其引⽤的常量的索引值设置为#0来表示。
常量池数据区
常量池
访问标志
访问标志,access_flags 是⼀种掩码标志,⽤于表示某个类或者接⼝的访问权限及基础属性
访问标志说明
类索引
this_class的值必须是对constant_pool表中项⽬的⼀个有效索引值。constant_pool表在这
个索引出的项必须为CONSTANT_Class_info 类型常量,表示这个 Class ⽂件所定义的类或接⼝。
⽗类索引
对于类来说,super_class 的值必须为 0 或者是对constant_pool 表中项⽬的⼀个有效索引值。如果它的值不为 0,那 constant_pool 表在这个索引处的项必须为CONSTANT_Class_info 类型常量,表示这个 Class ⽂件所定义的类的直接⽗类。当前类的直接⽗类,以及它所有间接⽗类的access_flag 中都不能带有ACC_FINAL 标记。对于接⼝来说,它的Class⽂件的super_class项的值必须是对constant_pool表中项⽬的⼀个有效索引值。constant_pool表在这个索引处的项必须为代表java.lang.Object 的 CONSTANT_Class_info 类型常量 。如果 Class ⽂件的 super_class的值为 0,那这个Class⽂件只可能是定义的是java.lang.Object类,只有它是唯⼀没有⽗类的类。
接⼝计数器
interfaces_count的值表示当前类或接⼝的直接⽗接⼝数量。
接⼝信息数据区
接⼝表,interfaces[]数组中的每个成员的值必须是⼀个对constant_pool表中项⽬的⼀个有效索引值, 它的⻓度为 interfaces_count。每个成员interfaces[i] 必须为 CONSTANT_Class_info类型常量,其中 【0 ≤ i
字段计数器
fields_count的值表示当前 Class ⽂件 fields[]数组的成员个数。 fields[]数组中每⼀项都是⼀个field_info结构的数据项,它⽤于表示该类或接⼝声明的【类字段】或者【实例字段】
字段信息数据区
字段表,fields[]数组中的每个成员都必须是⼀个fields_info结构的数据项,⽤于表示当前类或接⼝中某个字段的完整描述。 fields[]数组描述当前类或接⼝声明的所有字段,但不包括从⽗类或⽗接⼝继承的部分。
⽅法计数器
methods_count的值表示当前Class ⽂件 methods[]数组的成员个数。Methods[]数组中每⼀项都是⼀个 method_info 结构的数据项。
⽅法信息数据区
⽅法表,methods[] 数组中的每个成员都必须是⼀个 method_info 结构的数据项,⽤于表示当前类或接⼝中某个⽅法的完整描述。如果某个method_info 结构的access_flags 项既没有设置 ACC_NATIVE 标志也没有设置ACC_ABSTRACT 标志,那么它所对应的⽅法体就应当可以被 Java 虚拟机直接从当前类加载,⽽不需要引⽤其它类。method_info结构可以表示类和接⼝中定义的所有⽅法,包括【实例⽅法】、【类⽅法】、【实例初始化⽅法】和【类或接⼝初始化⽅法】。methods[]数组只描述【当前类或接⼝中声明的⽅法】,【不包括从⽗类或⽗接⼝继承的⽅法】。
属性计数器
属性计数器,attributes_count的值表示当前 Class ⽂件attributes表的成员个数。attributes表中每⼀项都是⼀个attribute_info 结构的数据项。
属性信息数据区
属性表,attributes 表的每个项的值必须是attribute_info结构。在Java 7 规范⾥,Class⽂件结构中的attributes表的项包括下列定义的属性: InnerClasses 、EnclosingMethod 、 Synthetic 、Signature、SourceFile,SourceDebugExtension 、Deprecated、RuntimeVisibleAnnotations 、RuntimeInvisibleAnnotations以及BootstrapMethods属性。
对于⽀持 Class ⽂件格式版本号为 49.0 或更⾼的 Java 虚拟机实现,必须正确识别并读取attributes表中的Signature、RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations属性。对于⽀持Class⽂件格式版本号为 51.0 或更⾼的 Java 虚拟机实现,必须正确识别并读取attributes表中的BootstrapMethods属性。Java 7 规范 要求任⼀ Java 虚拟机实现可以⾃动忽略Class ⽂件的 attributes表中的若⼲ (甚⾄全部) 它不可识别的属性项。任何本规范未定义的属性不能影响Class⽂件的语义,只能提供附加的描述信息。
个人学习整理,如有错误,欢迎指正。
领取专属 10元无门槛券
私享最新 技术干货