dustland

dustball in dustland

class字节码文件

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文件大体上线性地排列者以下几部分

image-20221230194129578

magic

文件魔术0xCAFEBABE

minor_version/major_version

副/主 版本号

描述本class文件可以被java几执行

image-20221230173614375

但是这个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

访问标志

两个字节表示的访问标志,用于描述本类的信息,多个属性按位或

image-20221230195212243
类型 类名 继承 access_flag
interface Measurable Object 0x0600(抽象|接口)
public class Point Object 0x0021(公共|子类)
class TaggedPoint Point 0x0020(子类)
final class Line Object 0x0030(final|子类)

索引集合

紧跟在访问标志之后,是

本类索引,

父类索引,

接口索引集合

image-20221230202234391

只有接口是一个集合,是因为java中只允许单继承,父类只能有一个,但是接口可以实现多个

索引值是一个下标,比如this_class=1,意思是去常量池中找1号常量

image-20221230202532520

tag=1表示utf8_info,即一个字符串

正好就是本类类名

又如super_class=3,意思是去常量池找3号常量

image-20221230202946050

也是一个tag=1,utf8字符串,正好是Object类名

this_class和super_class之后是interfaces_count,一个整数表示本类实现了几个接口,有一个算一个都要列在后面的interfaces索引数组中

image-20221230203155953

Point类只实现了一个Measurable接口他的索引号是5

image-20221230203241579

字段表集合

用于描述接口或者类中声明的变量

首先是一个整数fields_count描述类有几个字段

紧跟着就是对每个字段的描述

image-20221230205132151

这些描述包括:

access_flags,访问标志,包括static,public,final,volatile等等

name_index,符号名自己在常量池中的下标,也就是__x的下标

descriptor_index,类型名在常量池中的下标,也就是double的下标

attributes_count

access_flags

每一种属性占用一位,该位为0表示没有这种属性,为1表示有

共有16位,只用到9位

image-20221230205606249

对于double __x;,其属性是0x0004(protected)

image-20221230210219778

name_index

字段的简单名称

"简单名称"意思是没有加上类名前缀,

__x的完整名称应该是com/dustball/Point.__x

但是当前文件就是在描述Point类,显然没有必要再给其成员存放完整名称了

descriptor_index

描述符在符号表中的下标

描述符就是double,int等等的类型

只需要一个字符D就可以表明double类型

image-20221230211059690

额外属性

额外属性可能有多个,因此首先一个attributes_count整数,表明有多少个额外属性

有多少个就在后面列明多少个

这里__x没有额外属性

方法表集合

image-20221230211542677

方法表和字段表的结构比较相似

首先还是访问修饰

image-20221230211704703

然后是简单名称引用和描述符引用

这两个合起来就正这样

image-20221230212018387

<init>(DD)V这里<init>是构造函数名,(DD)表明两个double类型的参数,V表明无返回值

main([Ljava/lang/String;)V,这里main是主函数名([Ljava/lang/String;)注意这里中括号没有对齐,只有左中括号表明参数是一个数组类型

然后是attributes_count属性个数和attributes属性表

字段,方法都可以有属性表

方法编译成的opcode就放在其属性表中

属性表

Code属性

image-20221230212736701
image-20221230212754406
attribute_name_index=Code

表明这是Code属性

attribute_length

本属性的长度

max_stack

操作数栈深度最大值

max_locals

局部变量最大存储空间(单位:槽)

32位及以下数据用一个槽

64位及以上用两个槽

槽可以复用,函数中用完死了的变量给后面的新局部变量腾空

max_stack和max_locals两者共同决定了函数栈帧的大小

code_length

指令长度

code

指令码,可以类比作x86机器码

只不过这个指令是给JVM看的,x86机器码是给x86机器看的

image-20221230213315763

用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压缩包是相同的

image-20230104214655752

解压缩或者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包如何自己运行,用到时候再看吧,全是答辩