- 从
.java
到.class
- 如何查看
.class
javap
命令的使用- 字节码是什么
编译成为class文件
编写java文件
1
2
3
4
5
6
7package com.yuda;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}生成class文件
1
$ javac HelloWorld.java
查看class文件内容
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$ xxd HelloWorld.class
00000000: cafe babe 0000 0034 0022 0a00 0600 1409 .......4."......
00000010: 0015 0016 0800 170a 0018 0019 0700 1a07 ................
00000020: 001b 0100 063c 696e 6974 3e01 0003 2829 .....<init>...()
00000030: 5601 0004 436f 6465 0100 0f4c 696e 654e V...Code...LineN
00000040: 756d 6265 7254 6162 6c65 0100 124c 6f63 umberTable...Loc
00000050: 616c 5661 7269 6162 6c65 5461 626c 6501 alVariableTable.
00000060: 0004 7468 6973 0100 154c 636f 6d2f 7975 ..this...Lcom/yu
00000070: 6461 2f48 656c 6c6f 576f 726c 643b 0100 da/HelloWorld;..
00000080: 046d 6169 6e01 0016 285b 4c6a 6176 612f .main...([Ljava/
00000090: 6c61 6e67 2f53 7472 696e 673b 2956 0100 lang/String;)V..
000000a0: 0461 7267 7301 0013 5b4c 6a61 7661 2f6c .args...[Ljava/l
000000b0: 616e 672f 5374 7269 6e67 3b01 000a 536f ang/String;...So
000000c0: 7572 6365 4669 6c65 0100 0f48 656c 6c6f urceFile...Hello
000000d0: 576f 726c 642e 6a61 7661 0c00 0700 0807 World.java......
000000e0: 001c 0c00 1d00 1e01 000c 4865 6c6c 6f20 ..........Hello
000000f0: 576f 726c 6421 0700 1f0c 0020 0021 0100 World!..... .!..
00000100: 1363 6f6d 2f79 7564 612f 4865 6c6c 6f57 .com/yuda/HelloW
00000110: 6f72 6c64 0100 106a 6176 612f 6c61 6e67 orld...java/lang
00000120: 2f4f 626a 6563 7401 0010 6a61 7661 2f6c /Object...java/l
00000130: 616e 672f 5379 7374 656d 0100 036f 7574 ang/System...out
00000140: 0100 154c 6a61 7661 2f69 6f2f 5072 696e ...Ljava/io/Prin
00000150: 7453 7472 6561 6d3b 0100 136a 6176 612f tStream;...java/
00000160: 696f 2f50 7269 6e74 5374 7265 616d 0100 io/PrintStream..
00000170: 0770 7269 6e74 6c6e 0100 1528 4c6a 6176 .println...(Ljav
00000180: 612f 6c61 6e67 2f53 7472 696e 673b 2956 a/lang/String;)V
00000190: 0021 0005 0006 0000 0000 0002 0001 0007 .!..............
000001a0: 0008 0001 0009 0000 002f 0001 0001 0000 ........./......
000001b0: 0005 2ab7 0001 b100 0000 0200 0a00 0000 ..*.............
000001c0: 0600 0100 0000 0700 0b00 0000 0c00 0100 ................
000001d0: 0000 0500 0c00 0d00 0000 0900 0e00 0f00 ................
000001e0: 0100 0900 0000 3700 0200 0100 0000 09b2 ......7.........
000001f0: 0002 1203 b600 04b1 0000 0002 000a 0000 ................
00000200: 000a 0002 0000 0009 0008 000a 000b 0000 ................
00000210: 000c 0001 0000 0009 0010 0011 0000 0001 ................
00000220: 0012 0000 0002 0013 ........cafe babe
是class文件的魔数, 用来表示这个文件是个class文件, 很多文件都是以固定的字节开头作为魔数, 比如 PDF为0x255044462D
, png文件为0x89504E47
.0000 0034
魔数后4个字节分别表示副版本号(Minor Version)和主版本号(Major Version), 例子中52(0x34), 是主版本号, 对应了Java8, 其他版本对应如下:
Java 版本 | Major Version |
---|---|
Java 1.4 | 48 |
Java 5 | 49 |
Java 6 | 50 |
Java 7 | 51 |
Java 8 | 52 |
Java 9 | 53 |
- 紧接着后面是常量池, 分为两部分, 2字节用来保存长度信息和若干个常量池项. 目前Java虚拟机定义了14种常量类型例如 CONSTANT_Utf8_info (1), CONSTANT_Integer_info(3), CONSTANT_Float_info(4), CONSTANT_Long_info(5) CONSTANT_Double_info(6), CONSTANT_Class_info(7),CONSTANT_String_info(8), CONSTANT_Fieldref_info(9), CONSTANT_Methodref_info(10), CONSTANT_InterfaceMethodref_info(11), CONSTANT_NameAndType_info(12), CONSTANT_MethodHandle_info(15), CONSTANT_MethodType_info(16), CONSTANT_InvokeDynamic_info(18). 不同类型的常量项, 数据所占的大小也会不同.
- 常量池之后是类访问标记(Access flags), 用来标识一个类是否是final与abstract, 由2个字节表示, 一共16个标志位, 目前仅仅使用了其中8个.
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC | 1 | 标识是否是 public |
ACC_FINAL | 10 | 标识是否是 final |
ACC_SUPER | 20 | 已经不用了 |
ACC_INTERFACE | 200 | 标识是类还是接口 |
ACC_ABSTRACT | 400 | 标识是否是 abstract |
ACC_SYNTHETIC | 1000 | 编译器自动生成,不是用户源代码编译生成 |
ACC_ANNOTATION | 2000 | 标识是否是注解类 |
ACC_ENUM | 4000 | 标识是否是枚举类 |
- 然后是类、超类、接口索引表, 用来确定类的继承关系.
- 字段表(Fields), 类中的字段会保存在这个集合中(不包含方法中定义的变量), 可变长的.
- 方法表, 用来表示类中定义的方法, 同样是可变长的.
- 属性表, 也是class中最后一部分内容, 是可变长的, 预定义了 23 种属性, 用来表示类中字段表, 方法表等它们的属性信息.
javap命令的使用
1 | Usage: javap <options> <classes> |
主要的参数是
-c
-v
-l
-p
-s
字节码
Java 虚拟机的指令由一个字节长度的操作码(opcode)和紧随其后的可选的操作数(operand)构成, 所以称作
字节码
源码如下:
1 | package com.yuda; |
javap 解析后
1 | ••• yuda (master) javap -c -v -l -s -p HelloWorld.class |
其中 <opcode> [<operand1>, <operand2>]
格式的就是字节码, 由操作码和操作数组成, 操作码长度只有1个字节, 最多能表示256个类型, 目前已经有200+了. 字节码并不是机器码, 而是对机器码的一层抽象, 可以通过JIT(Just in time) 进一步编译成为机器码, 所以Java可以做到一次编译到处运行.