第六章_1 类文件结构
这章开始之前,我们先搞明白两个概念,机器码和字节码。
一、机器码与字节码
1)机器码:
机器语言指令,有时也被称为原生码(Native Code),是CPU直接读取运行的机器指令,运行速度最快。
2)字节码:
是一种包含执行程序、由一序列 op 代码/数据对 组成的二进制文件,字节码是一种中间码。编译器将源码编译成字节码,字节码在运行时通过JVM(JAVA虚拟机)做一次转换生成适合本机器环境的机器指令。
jvm的存在使得“一次编译,到处执行”成为可能。jvm负责把字节码文件直译成适合硬件环境的机器指令。同时java虚拟机不和包括java语言在内的任何语言进行绑定。它只与“Class”文件这种特定的二制式格式所关联。
二、Class文件生成
class文件是一组以8位字节为基础的二进制字节流。有其严格的结构规范。
java语言可以通过javac编译器将java文件编译为.class文件。
还是借助工具成看一下吧,这种字节码文件肉眼看太过晦涩。好在java为我们提供了一款工具javap
1)写一段简单的java程序
public class JavapTest {
public static void main(String[] args) {
int a = 2;
int b = 3;
int c = a + b;
System.out.println(c);
}
}
2)编译为class文件
javac JavapTest.java
这样生成了JavapTest.class文件,用文本编辑器打开看一下。
3)javap反编译
javap是 Java class文件分解器,变成一种我们可以直观看懂的格式。
javap -verbose JavapTest.class >JavapTest.txt
4) Code和元数据
Code:代码描述
元数据:(Metadata,包括类、字段、方法定义及其它信息)
二、分析class文件结构
Classfile /Users/liuyi/lhjr-springcould/lhjr-eureka/target/test-classes/com/lhjr/eureka/JavapTest.class
Last modified Nov 6, 2018; size 593 bytes
MD5 checksum 3ad6923189738afa920bd86bb2f372e3
Compiled from "JavapTest.java"
public class com.lhjr.eureka.JavapTest (类名)
minor version: 0 (次版本号)
major version: 52 (主版本号)
flags: ACC_PUBLIC, ACC_SUPER (类的访问修饰符)
高版本兼容低版本:
访问标志:
Constant pool: (常量池,可以理解为资源仓库)
#1 = Methodref #5.#23 // java/lang/Object."<init>":()V (方法引用,这是符号引用。到运行时才解析成直接引用)
#2 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream; (字段解析)
#3 = Methodref #26.#27 // java/io/PrintStream.println:(I)V
#4 = Class #28 // com/lhjr/eureka/JavapTest
#5 = Class #29 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 LocalVariableTable
#11 = Utf8 this
#12 = Utf8 Lcom/lhjr/eureka/JavapTest;
#13 = Utf8 main (方法名)
#14 = Utf8 ([Ljava/lang/String;)V (描述符,参数和返回值)
#15 = Utf8 args (字段名)
#16 = Utf8 [Ljava/lang/String; (字段描述符 [Ljava/lang/String代表 String[])
#17 = Utf8 a (字段名)
#18 = Utf8 I (字段描述符I代码 int类型)
#19 = Utf8 b (字段名)
#20 = Utf8 c
#21 = Utf8 SourceFile
#22 = Utf8 JavapTest.java
#23 = NameAndType #6:#7 // "<init>":()V (名称和返回值)
#24 = Class #30 // java/lang/System
#25 = NameAndType #31:#32 // out:Ljava/io/PrintStream;
#26 = Class #33 // java/io/PrintStream
#27 = NameAndType #34:#35 // println:(I)V
#28 = Utf8 com/lhjr/eureka/JavapTest
#29 = Utf8 java/lang/Object
#30 = Utf8 java/lang/System
#31 = Utf8 out
#32 = Utf8 Ljava/io/PrintStream;
#33 = Utf8 java/io/PrintStream
#34 = Utf8 println
#35 = Utf8 (I)V
常量池可以理解为class文件的资源仓库。常量池中存放两大类常量:
1、字面量(值)
对于java语言来说包括字符串String、final修鉓的字段值
2、符号引用(仅是符号引用,而不是真正的内存地址)
类名和接口的全限定名
字段的名称和描述符
方法名称和描述符
ps:class文件不会保存各方法、字段的最终内存布局信息,因些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址。
这里面的常量和我们java语言里面的常量是不同的。大家不要按java语言的常量来理解,以免给自己挖坑。
{
public com.lhjr.eureka.JavapTest(); (这是一个构造函数)
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0 (this)
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/lhjr/eureka/JavapTest;
如果类里面没有显示的定义构造函数,那么class文件里面会生成一个默认的构造函数。
这个结构先不讲,跟后面的main方法一起来说。
public static void main(java.lang.String[]); (这是main函数)
descriptor: ([Ljava/lang/String;)V (描述符)
描述符:
例如方法:int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)的描述符为([CII[CIII)I。
flags: ACC_PUBLIC, ACC_STATIC (方法访问描述符 public static)
Code: (具体执行的代码)
#stack操作数栈的深度
#locals代表本地变量表最大长度slot。32位一个,64位占两个
#args_size 代表方法的参数个数
stack=2, locals=4, args_size=1
0: iconst_2 (把常量2压入栈)
1: istore_1 (把栈顶的元素出栈存到本地变量表1中)
2: iconst_3 (把常量3压入栈)
3: istore_2 (把栈顶的元素出栈存到本地变量表2中)
4: iload_1 (从本地变量1的数据压栈)
5: iload_2 (从本地变量2的数据压栈)
6: iadd (把栈中元素出栈作加法,把结果压栈)
7: istore_3 (把加法的结果出栈存到本地变量表3中)
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; (获取常量池#2的引用)
11: iload_3 (从本地变量3的数据压栈)
12: invokevirtual #3 // Method java/io/PrintStream.println:(I)V (调用常量池#3的引用)
15: return (返回结束)
LineNumberTable: (行号对应表,代码的行号,对应字节码的行号)
line 5: 0
line 6: 2
line 7: 4
line 8: 8
line 9: 15
LocalVariableTable: (本地变量表,每个局部变量占1个Slot,double和long这种64位的数据类型占2个)
Start Length Slot Name Signature
0 16 0 args [Ljava/lang/String;
2 14 1 a I
4 12 2 b I
8 8 3 c I
}
SourceFile: "JavapTest.java" (用于记录生成这个class文件的源码文件名称)