类文件结构
首先实现语言无关性的基础仍然是虚拟机和字节码存储格式.Java虚拟机不和包括Java在内
的任何语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联,Class文件中包含
了 Java虚拟机指令集和符号表以及若干其他辅助信息。基于安全方面的考虑,Java虚拟机规
范要求在Class文件中使用许多强制性的语法和结构化约束,但任一门功能性语言都可以表示
为一个能被Java虚拟机所接受的有效的Class文件。作为一个通用的、机器无关的执行平台
这一篇主要还是讲有关Class文件的结构,这是JVM深入的基础。
Class类文件的结构
注意点:所有的接口和类都是对应着一个Class文件,但是并非所有的接口和类都需要以磁盘文件的方式存在,也可以直接通过类加载器生成。
Class文件是一组以8字节为基础的二进制流,所有的数据都是存在于此处的,而且中间没有任何分隔符等,只是简单的压缩在一起。当需要的时候会选择使用8字节进行分割。
JVM中是采用一种伪结构体的方式来存储数据的,无符号数+表
无符号数使用u1.u2,u4,u8来代表一字节~八字节(表示的是字节数,例如索引的字节数等等)
表就是多个无符号数和多个表等符合结构组成的数据,通常以_info结尾。
例如上面第一条,magic这个可以是索引也可以是数字,数量值等,但是他是4字节的。这一条的含义就是这样。
常量池
常量池是Class文件中的资源仓库,同时也是第一个表结构的数据,也是占用Class文件空间最大的项目之一。
由于常量池的数量不是固定的,所有一般在第一个数据的位置会有一个u2大小的来表示下常量池的数量,但是这个不一样的地方是从1开始,并不是同Java中从0来开始计算。这样做的目的是为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池的项目”的含义。这样的话这些索引值直接指向0就行。
这边就是0016,转化为10进行就是22,所以这个常量池里面会有21个常量。
常量池里面放的一般有两大类常量:字面量和符号引用
字面量:接近Java中的常量例如文本类字符串,final修饰的值
符号引用有三大类:接口和类的全限定名,字段的名称和描述符,方法的名称和描述符
这边要记住一个Class文件只对应一个类或者一个接口。
Java代码在Javac编译的时候,会进行动态链接,在Class文件中不会保留具体的引用,例如这个接口依赖的接口类的地址等等,所以需要在运行期转换的时候得到真正的内存入口地址。
另外常量池的每一个常量都是一个表。
访问标志
访问表示用来识别类或者接口的访问信息,例如这个Class是接口还是类,是否定义为public类,是否定义为abstract,如果是类的话是不是final型的。
没有使用到的标志位一致定义为0.
类索引,父类索引与接口索引
类索引(this_ class) 和父类索引(super_ class) 都是一个u2类型的数据,而接口索引集合(interfaces) 是一组u2类型的数据的集合,Class 文件中由这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。由于Java语言不允许多重继承,所以父类索引只有一个,除了java.lang Object之外,所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为0。接口索弓集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements 语句(如果这个类本身是-一个接口,则应当是exfends语句)后的接口顺序从左到在排列在接口索引集合中。
类索引、父类索引和接日索引集合都按顺序排列在访问标志之后,类索引和父类索引用两个u2类型的索引值表示,它们各自指向一个类型为CONSTANT_ Class_ info的类描述符常量,通过CONSTANT_ Class _info 类型的常量中的索引值咱以找到定义在CONSTANT_ Utf8_info类型的常量中的全限定名字符申。图6-6演示了代码清单6-1的代码的类索引查找过程。
字段表集合
字段表用于描述接口或者类中声明的变量。字段(filed)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。可以包括的信息例如字段的作用域,实例变量还是类变量,可变性,并发可见性,可否被序列化,字段数据类型,字段名称。
上面的这些值都是布尔类型的,要么为1要么为0所以很适合用标志位来进行表示。而字段叫什么,字段被定义为什么数据类型这些都是固定的,只能引用常量池里面的常量来表示。例如下图所示
这个很适合用access_flags标志来表示,跟随access_flags标志的是两项索引值:name_index和descriptor index.它们都是对常 最池的引用,分别代表着字段的简单名称以及字段和方法的描述符。现在需要解释一下“简 单名称”、“描述符”以及前面出现过多次的“全限定名”这三种特殊字符串的概念。
其余的方法区和属性区都是差不多的方法。
头文件分析:
public class JavaTest { private static String name = "JVM"; public static void main(String[] args) { System.out.println("Hello " + name); } }
出来的头文件是这样的
每个class文件的头4个字节称为魔数,用于确定这个文件是否能被虚拟机所接受。class文件的魔数值为CAFEBABE。
第5、6字节为次版本号,7、8字节为主版本号。Java的主版本号从45开始,JDK1.1之后每个大版本发布,主版本号加1。高版本的jdk能前向兼容之前版本的class文件,但不能运行以后版本的class文件。
从图1可以看到,次版本号为0000,主版本号为0031,这说明该class文件可以被1.5及以后版本的jdk运行。
CA FE BA BE 00 00 00 31=>202 254 186 190 00 00 00 49
1.1+0.4 = 1.5之后
这个代表了常量池中的数量,34=>52-1 = 51个
常量池中每一个常量都是一个表,jdk1.7之后一共有14种类型的常量,他们对应着14个不同结构的表,但这14个表都有一个共同特点:那就是表开始的第一位是一个u1类型的标志位,代表当前常量属于哪种常量类型。其取值和含义如下表所示:
例如第一个是0A,那么对应就是10,表中为类方法的符号引用,下面一个是000C=>12.......
bogon:Downloads shiyangsheng$ javap -v JavaTest.class Classfile /Users/shiyangsheng/Downloads/JavaTest.class Last modified 2018-3-17; size 842 bytes MD5 checksum fbb2370c6b7413a0636806a0e492224a Compiled from "JavaTest.java" public class com.youzan.shys.advice.JavaTest minor version: 0 major version: 49 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #12.#29 // java/lang/Object."<init>":()V #2 = Fieldref #30.#31 // java/lang/System.out:Ljava/io/PrintStream; #3 = Class #32 // java/lang/StringBuilder #4 = Methodref #3.#29 // java/lang/StringBuilder."<init>":()V #5 = String #33 // Hello #6 = Methodref #3.#34 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #7 = Fieldref #11.#35 // com/youzan/shys/advice/JavaTest.name:Ljava/lang/String; #8 = Methodref #3.#36 // java/lang/StringBuilder.toString:()Ljava/lang/String; #9 = Methodref #37.#38 // java/io/PrintStream.println:(Ljava/lang/String;)V #10 = String #39 // JVM #11 = Class #40 // com/youzan/shys/advice/JavaTest #12 = Class #41 // java/lang/Object #13 = Utf8 name #14 = Utf8 Ljava/lang/String; #15 = Utf8 <init> #16 = Utf8 ()V #17 = Utf8 Code #18 = Utf8 LineNumberTable #19 = Utf8 LocalVariableTable #20 = Utf8 this #21 = Utf8 Lcom/youzan/shys/advice/JavaTest; #22 = Utf8 main #23 = Utf8 ([Ljava/lang/String;)V #24 = Utf8 args #25 = Utf8 [Ljava/lang/String; #26 = Utf8 <clinit> #27 = Utf8 SourceFile #28 = Utf8 JavaTest.java #29 = NameAndType #15:#16 // "<init>":()V #30 = Class #42 // java/lang/System #31 = NameAndType #43:#44 // out:Ljava/io/PrintStream; #32 = Utf8 java/lang/StringBuilder #33 = Utf8 Hello #34 = NameAndType #45:#46 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #35 = NameAndType #13:#14 // name:Ljava/lang/String; #36 = NameAndType #47:#48 // toString:()Ljava/lang/String; #37 = Class #49 // java/io/PrintStream #38 = NameAndType #50:#51 // println:(Ljava/lang/String;)V #39 = Utf8 JVM #40 = Utf8 com/youzan/shys/advice/JavaTest #41 = Utf8 java/lang/Object #42 = Utf8 java/lang/System #43 = Utf8 out #44 = Utf8 Ljava/io/PrintStream; #45 = Utf8 append #46 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #47 = Utf8 toString #48 = Utf8 ()Ljava/lang/String; #49 = Utf8 java/io/PrintStream #50 = Utf8 println #51 = Utf8 (Ljava/lang/String;)V
紧接着常量池之后的两个字节表示访问标志,主要是用来标记类或者接口层次的一些属性。目标之定义了16个标志位中的8位,没有使用到的一律为0。 具体标志位如下表:
在访问标志之后,有3个用来确定一个类的继承关系的数据,按先后顺序分别是:
- 类索引:用于确定类的全限定名
- 父类索引:用于确定父类的全限定名
- 接口索引:用于描述类实现了哪些接口
- 000B+000C+0000=>11+12+0=>指向常量池的11 12 0=>
#11 = Class #40 // com/youzan/shys/advice/JavaTest#12 = Class #41 // java/lang/Object再后面也是一样的分析方法具体的可以看:https://www.jianshu.com/p/0ddf610991a5这一篇的记录,非常的详细!
smartcat.994