Class字节码文件
javac编译.java源文件之后生成的 class字节码文件
class文件其中都包含了什么?
java是如何编译链接的?
jar包和class的关系?
class文件能否理解为c语言编译生成的可重定位目标模块.o?
每个interface,每个类,都会编译生成一个class文件.即使两个类写到同一个java文件里了,编译后也会生成两个class文件
Point.java
1 | package com.dustball; |
这里面有一个接口Measurable,一个Point类及其子类TaggedPoint,一个final类Line
编译后生成了四个文件
1 | PS C:\Users\86135\Desktop\java\cmd\target\classes\com\dustball> ls |
直接010editor *.class
全部打开观察
class文件结构
整个class文件大体上线性地排列者以下几部分
magic
文件魔术0xCAFEBABE
minor_version/major_version
副/主 版本号
描述本class文件可以被java几执行
但是这个52是怎么来的?主版本号何曾到过52?
这是因为java的最初版本号是45,不是从0开始的
52是jdk1.8?然而我也不知道怎么换算的,可以从010editor模板代码中看出这一点
1 | string ClassFileOnComment(ClassFile &obj) |
constant_pool_count
常量池容量计数值,该值决定了紧接着的constant_pool[]数组的元素个数
constant_pool[]常量池
可以理解为可重定位目标模块中的符号表.symtab
这个数组的0下标位置是空出来不使用的,从1下标开始使用
0下标可以作为”空引用”,也就是说其他元素引用0号元素意味着引用个寂寞
奇怪的是,这个数组的每个元素可以不一样大
正常c语言的结构体数组,每个元素都是相同大小的结构体,即使有些域用不到也得空着占位
而对于constant_pool:
每个元素最开始必然有一个tag成员,表明该元素是何种类型,也就确定了其占地大小,
常量池中主要存放两大类常量:字面量Literal和符号引用Symbolic References
access_flags
访问标志
两个字节表示的访问标志,用于描述本类的信息,多个属性按位或
类型 | 类名 | 继承 | access_flag |
---|---|---|---|
interface | Measurable | Object | 0x0600(抽象|接口) |
public class | Point | Object | 0x0021(公共|子类) |
class | TaggedPoint | Point | 0x0020(子类) |
final class | Line | Object | 0x0030(final|子类) |
索引集合
紧跟在访问标志之后,是
本类索引,
父类索引,
接口索引集合
只有接口是一个集合,是因为java中只允许单继承,父类只能有一个,但是接口可以实现多个
索引值是一个下标,比如this_class=1,意思是去常量池中找1号常量
tag=1表示utf8_info,即一个字符串
正好就是本类类名
又如super_class=3,意思是去常量池找3号常量
也是一个tag=1,utf8字符串,正好是Object类名
this_class和super_class之后是interfaces_count,一个整数表示本类实现了几个接口,有一个算一个都要列在后面的interfaces索引数组中
Point类只实现了一个Measurable接口他的索引号是5
字段表集合
用于描述接口或者类中声明的变量
首先是一个整数fields_count描述类有几个字段
紧跟着就是对每个字段的描述
这些描述包括:
access_flags,访问标志,包括static,public,final,volatile等等
name_index,符号名自己在常量池中的下标,也就是__x
的下标
descriptor_index,类型名在常量池中的下标,也就是double的下标
attributes_count
access_flags
每一种属性占用一位,该位为0表示没有这种属性,为1表示有
共有16位,只用到9位
对于double __x;
,其属性是0x0004(protected)
name_index
字段的简单名称
“简单名称”意思是没有加上类名前缀,
__x
的完整名称应该是com/dustball/Point.__x
但是当前文件就是在描述Point类,显然没有必要再给其成员存放完整名称了
descriptor_index
描述符在符号表中的下标
描述符就是double,int等等的类型
只需要一个字符D就可以表明double类型
额外属性
额外属性可能有多个,因此首先一个attributes_count整数,表明有多少个额外属性
有多少个就在后面列明多少个
这里__x
没有额外属性
方法表集合
方法表和字段表的结构比较相似
首先还是访问修饰
然后是简单名称引用和描述符引用
这两个合起来就正这样
<init>(DD)V
这里<init>
是构造函数名,(DD)
表明两个double类型的参数,V表明无返回值
main([Ljava/lang/String;)V
,这里main
是主函数名([Ljava/lang/String;)
注意这里中括号没有对齐,只有左中括号表明参数是一个数组类型
然后是attributes_count属性个数和attributes属性表
字段,方法都可以有属性表
方法编译成的opcode就放在其属性表中
属性表
Code属性
attribute_name_index=Code
表明这是Code属性
attribute_length
本属性的长度
max_stack
操作数栈深度最大值
max_locals
局部变量最大存储空间(单位:槽)
32位及以下数据用一个槽
64位及以上用两个槽
槽可以复用,函数中用完死了的变量给后面的新局部变量腾空
max_stack和max_locals两者共同决定了函数栈帧的大小
code_length
指令长度
code
指令码,可以类比作x86机器码
只不过这个指令是给JVM看的,x86机器码是给x86机器看的
用javap 反编译也可以看到
1 | public com.dustball.Point(double, double); |
至于java自己造的给虚拟机看的那些指令,现在不想研究
关于jvm多重要多重要,感觉都是大量java码农吹出来的.不如看先看明白CSAPP
java编译链接
同一包下有两个文件,存在依赖关系
App.java
1 | package top.dustball; |
Point.java
1 | package top.dustball; |
其中App.java中,Main函数引用Point构造函数和toString函数
如果只编译App.java会报告找不到符号出错误
需要同时编译App.java和Point.java
1 | javac App.java Point.java |
在同一目录下生成Point.class和App.class文件
如果要用java执行App.class文件,需要推到包路径下java top/dustball/App
1 | PS C:\Users\86135\Desktop\vsJava\empire\src\main\java\top\dustball> javac App.java Point.java |
这两个class文件都有用,如果把Point.class搬走,只执行App.class会报告链接错误
1 | PS C:\Users\86135\Desktop\vsJava\empire\src\main\java> java top/dustball/App |
这个Point.class的位置在导入类的时候就决定了
1 | package top.dustball; |
也就是说必须得和App同一路径下
jar包
java archieve
文件魔数是504B0304,和zip压缩包是相同的
解压缩或者jar -xf 命令均可以拆jar包
1 | PS C:\Users\86135\Desktop\jar> jar -xf junit-4.13.2.jar |
解出来/org/junit和/junit两个目录下面都是class文件
因此实际上jar包可以理解为静态库,里面的class文件可以理解为可重定位目标模块
麻了麻了怎么用jar包,jar包如何自己运行,用到时候再看吧,全是答辩