Class字节码文件
javac编译.java源文件之后生成的 class字节码文件
class文件其中都包含了什么?
java是如何编译链接的?
jar包和class的关系?
class文件能否理解为c语言编译生成的可重定位目标模块.o?
每个interface,每个类,都会编译生成一个class文件.即使两个类写到同一个java文件里了,编译后也会生成两个class文件
Point.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| package com.dustball;
import java.lang.Math;
interface Measurable { public double getDistance(Point p); }
public class Point implements Measurable {
protected double __x; protected double __y;
public Point(double _x, double _y) { __x = _x; __y = _y; }
public String toString() { return "(" + Double.toString(__x) + "," + Double.toString(__y) + ")"; }
public double getDistance(Point p) { double dist = 0; dist = (__x - p.__x) * (__x - p.__x) + (__y - p.__y) * (__y - p.__y); return Math.sqrt(dist); } public static void main(String[] args) { Point A=new Point(2,5); System.out.println(A.toString()); } }
class TaggedPoint extends Point {
String __t;
TaggedPoint(String _t, double _x, double _y) { super(_x, _y); __t = _t; } }
final class Line {
public Point __A; public Point __B;
Line(Point _A, Point _B) { __A = _A; __B = _B; } }
|
这里面有一个接口Measurable,一个Point类及其子类TaggedPoint,一个final类Line
编译后生成了四个文件
1 2 3 4 5 6 7 8 9 10 11 12
| PS C:\Users\86135\Desktop\java\cmd\target\classes\com\dustball> ls
Directory: C:\Users\86135\Desktop\java\cmd\target\classes\com\dustball
Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2022/12/30 20:02 467 Line.class -a--- 2022/12/30 20:02 191 Measurable.class -a--- 2022/12/30 20:02 1004 Point.class -a--- 2022/12/30 20:02 456 TaggedPoint.class
PS C:\Users\86135\Desktop\java\cmd\target\classes\com\dustball> 010editor *.class
|
直接010editor *.class
全部打开观察
class文件结构
整个class文件大体上线性地排列者以下几部分
magic
文件魔术0xCAFEBABE
minor_version/major_version
副/主 版本号
描述本class文件可以被java几执行
但是这个52是怎么来的?主版本号何曾到过52?
这是因为java的最初版本号是45,不是从0开始的
52是jdk1.8?然而我也不知道怎么换算的,可以从010editor模板代码中看出这一点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| string ClassFileOnComment(ClassFile &obj) { switch(obj.major_version) { case 45: return "JDK 1.0 or JDK 1.1"; break; case 46: return "JDK 1.2"; break; case 47: return "JDK 1.3"; break; case 48: return "JDK 1.4"; break; case 49: return "JDK 1.5"; break; case 50: return "JDK 1.6"; break; case 51: return "JDK 1.7"; break; case 52: return "JDK 1.8"; break; case 53: return "JDK 1.9"; break; default: return "JDK ?.?"; break; } }
|
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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public com.dustball.Point(double, double); descriptor: (DD)V flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=5, args_size=3 0: aload_0 1: invokespecial #13 // Method java/lang/Object."<init>":()V 4: aload_0 5: dload_1 6: putfield #16 // Field __x:D 9: aload_0 10: dload_3 11: putfield #18 // Field __y:D 14: return LineNumberTable: line 14: 0 line 15: 4 line 16: 9 line 17: 14 LocalVariableTable: Start Length Slot Name Signature 0 15 0 this Lcom/dustball/Point; 0 15 1 _x D 0 15 3 _y D MethodParameters: Name Flags _x _y
|
至于java自己造的给虚拟机看的那些指令,现在不想研究
关于jvm多重要多重要,感觉都是大量java码农吹出来的.不如看先看明白CSAPP
java编译链接
同一包下有两个文件,存在依赖关系
App.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package top.dustball; import top.dustball.Point;
public class App {
public static void main( String[] args ) {
Point p=new Point(2,5); System.out.println(p.toString()); } }
|
Point.java
1 2 3 4 5 6 7 8 9 10 11 12
| package top.dustball; public class Point { double x; double y; public Point(double x,double y){ this.x=x; this.y=y; } public String toString(){ return "("+x+","+y+")"; } }
|
其中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 2 3 4 5 6 7
| PS C:\Users\86135\Desktop\vsJava\empire\src\main\java\top\dustball> javac App.java Point.java PS C:\Users\86135\Desktop\vsJava\empire\src\main\java\top\dustball> java App 错误: 找不到或无法加载主类 App 原因: java.lang.NoClassDefFoundError: top/dustball/App (wrong name: App) PS C:\Users\86135\Desktop\vsJava\empire\src\main\java\top\dustball> cd ../../ PS C:\Users\86135\Desktop\vsJava\empire\src\main\java> java top/dustball/App (2.0,5.0)
|
这两个class文件都有用,如果把Point.class搬走,只执行App.class会报告链接错误
1 2 3 4 5
| PS C:\Users\86135\Desktop\vsJava\empire\src\main\java> java top/dustball/App (2.0,5.0) PS C:\Users\86135\Desktop\vsJava\empire\src\main\java> java top/dustball/App 错误: 加载主类 top.dustball.App 时出现 LinkageError java.lang.ClassFormatError: Incompatible magic value 1347093252 in class file top/dustball/App
|
这个Point.class的位置在导入类的时候就决定了
1 2
| package top.dustball; import top.dustball.Point;
|
也就是说必须得和App同一路径下
jar包
java archieve
文件魔数是504B0304,和zip压缩包是相同的
解压缩或者jar -xf 命令均可以拆jar包
1 2 3 4 5 6 7 8 9 10 11 12
| PS C:\Users\86135\Desktop\jar> jar -xf junit-4.13.2.jar PS C:\Users\86135\Desktop\jar> ls
Directory: C:\Users\86135\Desktop\jar
Mode LastWriteTime Length Name ---- ------------- ------ ---- d---- 2021/2/13 17:31 junit d---- 2021/2/13 17:31 META-INF d---- 2021/2/13 17:31 org -a--- 2023/1/3 21:30 384581 junit-4.13.2.jar -a--- 2021/2/13 17:31 11376 LICENSE-junit.txt
|
解出来/org/junit和/junit两个目录下面都是class文件
因此实际上jar包可以理解为静态库,里面的class文件可以理解为可重定位目标模块
麻了麻了怎么用jar包,jar包如何自己运行,用到时候再看吧,全是答辩