JVM 字节码文件原理详解

一、进制知识

在这里插入图片描述
我们看到的字节码文件是十六进制。

我们用魔数cafebabe来练习一下进制转换:

十六进制:c a f e b a b e
十进制: 12 10 15 14 11 10 11 14
二进制:1100 1010 1111 1110 …

二、不同语言在JVM上运行的本质

在这里插入图片描述
只要是复合JVM规范的字节码文件都能在虚拟机上运行。

三、小端与大端

大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。

小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

在这里插入图片描述
在这里插入图片描述
大端: 高地址存储数据的低位,低地址存储数据的高位;
小端: 高地址存储数据的高位,低地址存储数据的低位.、

在写JVM的时候,需要使用大端模式读取。

网络传输的时候是大端模式,主机存储的时候是小端模式。
计算机早起是大端模式,后来改成了小端模式。

cafe babe 0000 0031 0057 0a00 0f00 290a

如果是小端读取
be ba ca fe
如果是大端读取
ca fe ba be

注意,大端、小端只是从内存中读取数据的方式而已。

四、手动解析字节码文件准备工作

一个字节码文件由以下几部分组成:
在这里插入图片描述
U4 代码表占4个字节,!表示不确定占用的大小。

常量池为U2,占两个字节,两位最大的十六进制是FF FF, 转化为二进制是65535,所以最大可以有65535个常量。

  • magic:魔数,在class文件开头的四个字节, 存放着class文件的魔数, 这个魔数是class文件的标志,他是一个固定的值: 0XCAFEBABE 。 也就是说他是判断一个文件是不是class格式的文件的标准, 如果开头四个字节不是0XCAFEBABE, 那么就说明它不是class文件, 不能被JVM识别。

  • minor version、major version:class文件的此版本号和主版本号。 不同版本的javac编译器编译的class文件, 版本号可能不同, 而不同版本的JVM能识别的class文件的版本号也可能不同, 一般情况下, 高版本的JVM能识别低版本的javac编译器编译的class文件, 而低版本的JVM不能识别高版本的javac编译器编译的class文件。 如果使用低版本的JVM执行高版本的class文件, JVM会抛出java.lang.UnsupportedClassVersionError 。
    在这里插入图片描述

  • constant_pool_count: 常量池的数量不固定,需要2个字节来表示常量池容量计数值;注意,常量池最小的index是1,不是0;真实的常量池个数 = 字节码文件中的常量值个数-1;为什么这样设计呢?因为它把第0项常量空出来了:这是为了在于满足后面某些指向常量池的索引值的数据在特定情况下需要表达"不引用任何一个常量池项目"的含义,这种情况可用索引值0来表示.

  • constant_pool: 常量池,大小不确定,所以使用“ !”表示大小;下图是常量池中的11中数据类型:
    在这里插入图片描述

五、手动解析字节码文件

测试代码:


public class Test_1 {
    static int  a = 18;

    public static void main(String[] args) {

    }
}

十六进制的字节码文件:

cafe babe 0000 0034 0019 0a00 0400 1509
0003 0016 0700 1707 0018 0100 0161 0100
0149 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 124c 6f63
616c 5661 7269 6162 6c65 5461 626c 6501
0004 7468 6973 0100 204c 636f 6d2f 6a69
6875 2f74 6573 742f 6279 7465 5f63 6f64
652f 5465 7374 5f31 3b01 0004 6d61 696e
0100 1628 5b4c 6a61 7661 2f6c 616e 672f
5374 7269 6e67 3b29 5601 0004 6172 6773
0100 135b 4c6a 6176 612f 6c61 6e67 2f53
7472 696e 673b 0100 083c 636c 696e 6974
3e01 000a 536f 7572 6365 4669 6c65 0100
0b54 6573 745f 312e 6a61 7661 0c00 0700
080c 0005 0006 0100 1e63 6f6d 2f6a 6968
752f 7465 7374 2f62 7974 655f 636f 6465
2f54 6573 745f 3101 0010 6a61 7661 2f6c
616e 672f 4f62 6a65 6374(constant pool end) 0021 0003 0004
0000 0001 0008 0005 0006 0000 0003 0001
0007 0008 0001 0009 0000 002f 0001 0001
0000 0005 2ab7 0001 b100 0000 0200 0a00
0000 0600 0100 0000 0300 0b00 0000 0c00
0100 0000 0500 0c00 0d00 0000 0900 0e00
0f00 0100 0900 0000 2b00 0000 0100 0000
01b1 0000 0002 000a 0000 0006 0001 0000
0008 000b 0000 000c 0001 0000 0001 0010
0011 0000 0008 0012 0008 0001 0009 0000
001e 0001 0000 0000 0006 1012 b300 02b1
0000 0001 000a 0000 0006 0001 0000 0004
0001 0013 0000 0002 0014 

5.1、魔数(magic)
U4 cafe babe :ca fe ba be, 魔数,用来验证该字节码文件是否合法;
5.2、次版本号(minor version)
U2 次版本号:00 00 ;
5.3、主版本号(major version)
U2 主版本号:00 34,十进制数为 52,从上面的JDK版本图中可以确定主版本号为JDK1.8;
5.4、常量池个数(constant pool count)
U2 常量池个数:00 19, 十进制数为25,我们使用jclasslib工具分析,结果也是25;
在这里插入图片描述
在这里插入图片描述
但是我们需要注意到一点,从常量值中只看到了1~24个常量,原因在上面有提及到。
接下来,我们来开始解析常量池。

5.5、常量池(constant pool)
在这里插入图片描述

  • 第一个常量元素:
    tag: U1 0a, 十进制数为10,我们在上图中找到值为10的,可以看到对应的类型是Constant_Methodref_info,即方法, 那么method的信息在常量池中是如何存储的呢?
    在这里插入图片描述
    那么第一个常量元素为:
CONSTANT_Methodref_info {
	U1 tag: 0a;
	U2 class_index: 00 04// 这里是符号引用,因此此时在解析的时候不知道该值表示的是什么,只能使用字符串来表示符号引用
	U2 name_and_type_index 00 15;
}

第二个元素为:tag = 9, 对应的类型是CONSTANCE_Fieldref_info,即属性,存储如下:
在这里插入图片描述

CONSTANCE_Fieldref_info {
	U1 tag: 0a;
	U2 class_index: 00 03// 十进制 3 及对应下面的第三个元素,这里的符号引用
	U2 name_and_type_index 00 16;
}

第三个元素:tag=07,对应的类型是Constant_Class_info,结构如下:
在这里插入图片描述

CONSTANCE_Class_info {
	U1 tag: 07;
	U2 class_index: 00 17}

第四个元素:tag=7,已经分析过了,参考第三个元素;

第五个元素:tag=01,对应的类型是Constant_Utf8_info,结构如下:
在这里插入图片描述

CONSTANCE_Utf8_info {
	U1 tag: 01;
	U2 length: 00 01;   // 字符串长度
	U1 bytes: 61; // 字符串内容,61转换为十进制是97,ASCII对应的编码是a
}

常量池的解析到这里为止,我们接下来继续解析

5.6、类的访问控制权限(access flags)
在这里插入图片描述
可以从字节码文件中看到值为00 21是public;

5.7、类名(this_class)U2

U2 00 03, 对应的是Test_1.java

5.8、父类名(super_class)
U2 00 04, 对应的是java.lant.Object.

5.9、接口数量(interface_count)
U2 00 00,该类没有继承接口;

注意,如果interface_count为0,则下面的实现的接口(interfaces[ ] 则不会出现)

5.10、实现的接口 (interface [ ])

5.11、属性数量(fields_count)
U2 00 01,即我们只定义了一个属性,即a;

5.12、属性(fields_info)
在这里插入图片描述
第一个属性:

field_info {
	U2 access_falg: 00 08;   // static
	U2 name_index: 00 05;  // 
	description: 00 06   // 字段描述符 
	attributes_count:    // 竖向的属性数量,比如final修饰的属性,会有一个对应的ConstantValue
	attributets:   // 如果上面的attributes_count = 0,这一块也不会在字节码文件中出现
}

下面是字段的描述符:
在这里插入图片描述
byte[]数组的描述符是:[B ;(PS:一个 “[“ 表示一维数组,两个表示二维数组)

Sttring[]数组的描述符是:[Ljava/lang/String; ;

mian方法( public static void main(String[] args) {)的描述符:[(Ljava/lang/String;)V ;

方法的描述符的规则:(数据类型的描述符)返回值的描述符
Eg:
String Xxx(String [][] strArrs, int a, JIhu jihu) ,对应的描述符为:
([[Ljava/lang/String;, I, Ljava/test/Jihu)Ljava/lang/String;

5.13、方法数量(最难,methods_count)

U2:00 03

这里表示在测试代码中有三个方法:
在这里插入图片描述

  • clinit: 类中有静态属性或者静态代码块,编译的时候会自动生成这个方法(只会生成一个)
  • init: 默认的构造方法
  • main: 定义的main方法

5.14、解析方法(最难,methods_info)

method 
	Code
		LineNumberTable // java代码Xx行出错,就是查询了该table
		LocalVariableTable  // 存储参数和局部变量
	Exception	

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第一个方法:

method_info {
	u2	access_flags: 00 01
	u2	name_index: 00 07	//  这里指的是编号为07的常量池中的数据,从解析来看是init方法
	u2	descriptor_index: 00 08
	u2	attributes_count: 00 01
	u2	attribute_info;   // 如果属性个数为0,则没有整个区域
		// 00 09
		u2	attribute_name_index: 00 09 //  (code)
		u4	attribute_length: 00 00 00 2f	// 后面的数据栈的字节大小
		u2	max_stack: 00 01	// 操作数栈大小	
		u2	max_locals: 00 01	//  局部表大小
		u4	code_length: 00 00 00 05 //  方法体、字节码指令大小
		u1 	code: 2a b7 00 01 b1	// 方法体、字节码指令 这里存的是硬编码
		u2	exception _length: 0	// 0 异常 如果为0,则没有
		u2	attribute_count: 00 02	// code的属性
		[
			u2 attribute_name_index: 00 0a  // 我们查看常量池中第10个,发现对应的是LineNumberTable
			u4 attribute_length; 00 00 00 06	// 属性大小
			u2	length: 00 01
			[
				u2	start_pc: 00 01
				u2	line_number: 00 07
			]
			
			u2	attribute_name_ index: 00 0b  // 00 0b localVariableTable 局部变量表
			u4	attribute_length: 00 00 00 0c
			u2	table_length: 00 01
			[
				u2	start_pc: 00 00
				u2	length: 00 05
				u2	name_index: 00 0c
				u2	desc_index: 00 0d
				u2	index: 00 00 
			]

		]	
}

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值