编译与执行
类文件结构
- class文件没有分割符,全是数据,本质上是01010的编码
- class文件结构逻辑上以8个字节为单位,当数据项超过8个字节时, 则会按照高位在前的方式分割。
- class文件采用的类似c语言的结构体的方式存储数据,分为两种类型
- 无符号数:是基本的数据类型, 以u1、 u2、 u4、 u8来分别代表1个字节、 2个字节、 4个字节和8个字节的无符号数, 无符号数可以用来描述数字、 索引引用、 数量值或者按照UTF-8编码构成字符串值。
- 表:由无符号数和表构成的复合数据类型。整个Class文件就是一张表。
- 集合:无论是无符号数还是表, 当需要描述同一类型但数量不定的多个数据时, 经常会使用一个前置的容量计数器加若干个连续的数据项的形式, 这时候称这一系列连续的某一类型的数据为某一类型的“集合”。
- class文件数据中固定/规整的部分,直接使用规定确定死,不固定/变动的部分,采用前置的容量计数器+数据项的形式搞定。
常量池
- 前2个字节表示常量池的常量个数(constant_pool_count),从1开始
- 每个常量都是一个表
- 常量分为两类
- 字面量:文本字符串、 被声明为final的常量值等
- 符号引用:属于编译原理方面的概念,主要包括
- 被模块导出或者开放的包(Package)
- 类和接口的全限定名(Fully Qualified Name)
- 字段的名称和描述符(Descriptor)
- 方法的名称和描述符
- 方法句柄和方法类型(Method Handle、 Method Type、 Invoke Dynamic)
- 动态调用点和动态常量(Dynamically-Computed Call Site、 Dynamically-Computed Constant)
java编译时没有连接,而是在类文件被加载到虚拟机中时,类加载的步骤中做的连接,主要是讲常量池中的符号引用替换为具体的内存地址。
- 我对常量池的理解
- 常量池描述了啥
- 类或接口的描述:类/接口全名,包括本类及本类中使用到类,如使用继承了某个类,实现某个接口,使用了某个类的属性或方法等
- 属性的描述:类/接口全名+属性类型名+属性类型值
- 方法的描述:类/接口全名+方法名+形参类型名列表+返回值类型
- 方法句柄等待补充
- 为了描述这些东西定义一些数据结构,即17中常量类型
- 常量池描述了啥
- 常量的表示
- 常量的类型,即表的结构,共定义了17种,详见表6-3。标志就是类型的码值。除constant_utf8_info(表6-5)外,都是规整的。如constant_class_info(表6-4),一共
- 常量的类型,即表的结构,共定义了17种,详见表6-3。标志就是类型的码值。除constant_utf8_info(表6-5)外,都是规整的。如constant_class_info(表6-4),一共
类信息部分:类的访问标记+类全名+父类全名+实现的接口数+实现接口名列表
字段表(字段集合)=字段个数+字段表
- 字段=访问标记(static,final,volatile,transition等)+字段描述(=类全名+字段类型+字段名+字段的值)
- 字段包括:类字段+实例字段,不包括局部变量
- 字段表集合中不会列出从父类或者父接口中继承而来的字段
- 有可能出现原本Java代码之中不存在的字段, 譬如在内部类中为了保持对外部类的访问性, 编译器就会自动添加指向外部类实例的字段。
- Java语言中字段是无法重载的, 两个字段的数据类型、 修饰符不管是否相同, 都必须使用不一样的名称, 子类可以覆写父类的属性,
- 对于Class文件格式来讲, 只要两个字段的描述符不是完全相同, 那字段重名就是合法的。
- 类似的还有方法签名。
方法表(方法集合)=方法个数+方法表
- 方法=访问标记(static,final,synchronized,native,abstract等)+方法描述(=类全名+方法名+形参类型列表+返回值列表+方法体的编译后的字节码)
- java语言中方法签名为类全名+方法名+形参类型列表,不包括返回值,因此仅有返回值不一样不能重载。但是class文件中可以。
- 不包括父类的方法
- 有编译器自动生成的方法,常见的如
- 类构造器:() 内容待补充
- 实例构造器 :() 内容待补充
属性表(属性集合)=属性个数+属性表
- 常见的属性表类型:
- 字段相关:字段值
- 方法相关:方法编译后的字节码指令,异常表,局部变量表(源码局部变量名称与局部变量的映射,非必须),行号表(源码行号与字节码指令行号的映射非必须)
- 签名:因为编译会泛型擦除,所以需要记录泛型签名
- 源文件名
Code:方法编译后的字节码指令
- code=max_stack+max_locals+字节码长度+字节码指令+异常表长度+异常表(是方法抛出的异常而非异常表)+属性表长度+属性表
- max_stack:代表了操作数栈(Operand Stack) 深度的最大值。 在方法执行的任意时刻, 操作数栈都不会超过这个深度。 虚拟机运行的时候需要根据这个值来分配栈帧(Stack Frame) 中的操作栈深度。
- max_locals代表了局部变量表所需的存储空间。 在这里, max_locals的单位是变量槽(Slot) , 变量槽是虚拟机为局部变量分配内存所使用的最小单位。 对于byte、 char、 float、 int、 short、 boolean和returnAddress等长度不超过32位的数据类型, 每个局部变量占用一个变量槽, 而double和long这两种64位的数据类型则需要两个变量槽来存放。 方法参数(包括实例方法中的隐藏参数“this”) 、 显式异常处
理程序的参数(Exception Handler Parameter, 就是try-catch语句中catch块中所定义的异常) 、 方法体中定义的局部变量都需要依赖局部变量表来存放。 注意, 并不是在方法中用了多少个局部变量, 就把这些局部变量所占变量槽数量之和作为max_locals的值, 操作数栈和局部变量表直接决定一个该方法的栈帧所耗费的内存, 不必要的操作数栈深度和变量槽数量会造成内存的浪费。 Java虚拟机的做法是将局部变量表中的变量槽进行重用, 当代码执行超出一个局部变量的作用域时, 这个局部变量所占的变量槽可以被其他局部变量所使用, Javac编译器会根据变量的作用域来分配变量槽给各个变量使用, 根据同时生存的最大局部变量数量和类型计算出max_locals的大小。
异常表
行号表
出错时的堆栈信息中行号,调试时断电