微信公众号:运维开发故事,作者:老郑

概述

=====

概述本文主要是基于 .class 文件,进行分析 .class 文件的内容。

这部分个人觉得主要是属于设计机构拓展的内容,大家可以一起来学习一下 Java 字节码的设计结构以及感受一下设计者的设计。

class 类文件结构

Java 提供 javap 命令可以分析字节码文件,我们可以使用 javap -verbose 命令分析一个字节码文件时, 将会分析该字节码文件的魔数、版本号、常量池、类信息、类的构造方法、类中的方法信息、类变量与成员变量等信息。

一个简单的 Java 代码

public class TestClass {private int m;public int inc() {return ++m;}
}

下图显示的是 Java 代码编译后 .class 文件的十六进制信息

bytecode_十六进制.png

为了方便对比我执行一下 javap -v TestClass

Classfile /../../TestClass.classLast modified 2021-2-6; size 306 bytesMD5 checksum eeba40cc40cc28ef4d416ff70d901561Compiled from "TestClass.java"
public class cn.edu.cqvie.jvm.bytecode.TestClassminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref          #4.#15         // java/lang/Object."<init>":()V#2 = Fieldref           #3.#16         // cn/edu/cqvie/jvm/bytecode/TestClass.m:I#3 = Class              #17            // cn/edu/cqvie/jvm/bytecode/TestClass#4 = Class              #18            // java/lang/Object#5 = Utf8               m#6 = Utf8               I#7 = Utf8               <init>#8 = Utf8               ()V#9 = Utf8               Code#10 = Utf8               LineNumberTable#11 = Utf8               inc#12 = Utf8               ()I#13 = Utf8               SourceFile#14 = Utf8               TestClass.java#15 = NameAndType        #7:#8          // "<init>":()V#16 = NameAndType        #5:#6          // m:I#17 = Utf8               cn/edu/cqvie/jvm/bytecode/TestClass#18 = Utf8               java/lang/Object
{public cn.edu.cqvie.jvm.bytecode.TestClass();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 3: 0public int inc();descriptor: ()Iflags: ACC_PUBLICCode:stack=3, locals=1, args_size=10: aload_01: dup2: getfield      #2                  // Field m:I5: iconst_16: iadd7: dup_x18: putfield      #2                  // Field m:I11: ireturnLineNumberTable:line 8: 0
}
SourceFile: "TestClass.java"

Java 字节码结构

bytecode_结构.png

1. 魔数和 Class 文件版本

魔数:所有的.class 字节码文件的前4个字节都是魔数,魔数为固定值: 0xCAFEBABE

版本信息,魔数之后的4个字节是版本信息,前两个字节表示 minor version (次版本号), 后2个字节表示major version (主版本号)。这里的版本号 00 00 00 34 换算成十进制表, 表示次版本号为0, 主版本号为 52. 所以该文件的版本号为 1.8.0。可以通过 java -version 来验证这一点。

➜  ~ java -version
java version "1.8.0_281"
Java(TM) SE Runtime Environment (build 1.8.0_281-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.281-b09, mixed mode)

2. 常量池

  1. 常量池 (constant pool): 2+N个字节 紧接着主版本号之后的就是常量池入口。一个java 类中定义的很多信息都是由常量池来描述的,可以将常量池看作是 Class 文件的资源仓库,比如说Java类中变量的方法与变量信息,都是存储在常量池中。常量池中主要存储2类常量:字面量与符号引用。
  • 字面量, 如字符串文本,java 中声明为final 的常量值等。

  • 符号引用, 如类和接口的全局限定名, 字段的名称和描述符,方法的名称和描述符等。

  1. 常量池的总体结构:Java类所对应的常量池主要由常量池(常量表)的数量与常量池数组这两部分共同构成。常量池中常量数量紧跟着在主版本号后面,占据2字节: 常量池长度 比如这里我们的十六进制就是 00 13 代表有 18 个常量。常量数组则紧跟着常量池数量之后。常量池数组与一般数组不同的是, 常量池数组中不同的元素的类型,结构都是不同的。长度当然也就不同;但是, 一种元素的第一种元素的第一个数据都是一个u1类型, 该字节是一个标识位,占据1个字节。JVM在解析常量池时,会更具这个u1 类型来获取元素的具体类型。值得注意的是:常量池中元素的个数 = 常量池数 -1 (其中0暂时不适用), 目的是满足某些常量池索引值的数据在特定情况下需要表达【不引用任何一个常量池】的含义:根本原因在于,索引为0也是一个常量(保留常量),只不过他不位于常量表中。这个常量就对应null值, 所以常量池的索引是从1开始而非0开始。上面表中描述了11种数据类型的机构, 其实在jdk1.7之后又增加了3种(CONSTANT_MethodHandle_info, CONSTANT_MethodType_info 以及CONSTANT_InvokeDynami_info)。这样一共14种。

  2. 在JVM规范中, 每个变量/字段都有描述信息, 描述信息主要的作用是描述字段的数据类型、方法的参数列表(包括数量、类型、顺序)与返回值。根据描述符 规则, 基本数据类型和代表无返回值的的void 类型都用一个大写字符来表示, 对象类型则使用字符L加对象的全限定名称来表示。为了压缩字节码文件的体积 对于基本数据类型,JVM都只使用一个大写字母来表示,如下所示:B-byte, C-char, D-double, F-float, I-int, J-long, S-short, Z-boolean , V -void L -表示对象类型,如: Ljava/lang/String;

  3. 对于数组类型来说,每一个维度使用一个前置的 [来表示, 如int[] 被标记为 [I , String[][]被表示为 [[java/lang/String;

  4. 用描述符描述方法时, 按照先参数列表, 后返回值的顺序来描述. 参数列表按照参数的严格顺序放在一组()内, 如方法: String getRealnameByIdNickname(int id, String name)的描述符为: (I, Ljava/lang/String;) Ljava/lang/String

Class 字节码中有两种数据类型

  • 字节数据直接量:这是基本的数据类型。共细分为u1、u2、u4、u8四种,分别代表连续的1个字节、2个字节、4个字节、8个字节组成的整体数据。

  • 表(数组):表示由多个基本数据或其他表,按照既定顺序组成的大的数据集合。表是有结构的 。它的结构体现在:组成表的成分所在的位置和顺序都是 严格定义好的。

常量池常量

  1. 0A 00 04 00 0F method_info 00 04 指向常量池中的常量的位置, 00 0F 指向的是 15 的位置。

  2. 09 00 03 00 10 field_info 00 03 指向 3 的位置 00 10 (16 位置)

  3. 07 00 11 class info 00 11 (17 位置)

  4. 07 00 12 class info 00 12 (18 位置)

  5. 01 00 01 6D utf8 长度为 1 内容为 6D 表示内容转换为 10 进制为 109 转换为 ASCII 码最后的结果为 m

  6. 01 00 01 49 utf8 长度为 1 内容为 I

  7. 01 00 06 3C 69 6E 69 74 3E utf8 长度为 8 内容为 <init>

  8. 01 00 03 28 29 56 utf8 长度为 3 内容为:()V

  9. 01 00 04 43 6F 64 65 utf8长度为 4 内容为 Code

  10. 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 utf8 长度为 15 内容为 LineNumberTable

  11. 01 00 03 69 6E 63 utf8 长度为3 内容为 inc

  12. 01 00 03 28 29 49 长度为 3 内容为 ()I

  13. 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 长度为 10 内容为 SourceFile

  14. 01 00 0E 54 65 73 74 43 6C 61 73 73 2E 6A 61 76 61 长度为 14 内容为 TestClass.java

  15. 0C 00 07 00 08 NameAndType 内容 指向 7 位置。00 08 (8 位置)

  16. 0C 00 05 00 06 NameAndType 内容 指向 5 位置。00 06 (6 位置)

  17. 01 00 23 63 6E 2F 65 64 75 2F 63 71 76 69 65 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 54 65 73 74 43 6C 61 73 73 长度 35 内容 cn/edu/cqvie/jvm/bytecode/TestClass

  18. 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 长度 16 内容 java/lang/Object

3. 访问标志

Access_Flag 访问标志 访问标识信息包括该Class文件时类和接口是否被定义成了public,是否是 abstract, 如果是类,是否被申明为成final。通过扇面的源代码。

0x 00 21: 表示是0x0020 和0x0001的并集, 表示 ACC_PUBLICACC_SUPER

bytecode_访问标志.png

4. 类索引、父类索引

00 03 类名, 03 常量池位置 cn/edu/cqvie/jvm/bytecode/TestClass

00 04 父类名. 04 常量池位置 java/lang/Object

00 00 接口个数, 0 个, 接口个数最多 65535

5. 字段表集合

00 01 字段的个数, 这里有一个

bytecode_字段表.png

字段表结构

field_info {u2 access_flags; 00 02 Fieldrefu2 name_index; 00 05 (表示字段名称在常量池中的索引位置) mu2 descriptor_index; 00 06 (描述符索引) I u2 attributes_count; 00 00attribute_info attributes[attributes_count]
}

6. 方法表集合

00 02 表示有两个方法

bytecode_方法表.png

方法表结构

method_info {u2 access_flags; 00 01  Methodrefu2 name_index;  // 00 07  <init>u2 descriptor_index; 00 08 // ()Vu2 attributes_count; 00 01 // 属性结构attributes_info attributes[attributes_count]
}

方法属性结构

attribute_info {u2 attribute_name_index; 00 09 // Codeu4 attribute_length; 00 00 00 1D 长度 29u1 info[attribute_length];  ...
}

7. 属性表集合

00 09 00 00 1D 00 01 00 01 00 00 00 05 2A B7 00 01 B1 00 00 00 01 00 0A 00 00 00 06 00 01 00 00 00 03 00

“Code” 表示下面是执行代码

JVM 预定义了一部分的attribute, 但是编译器自己也可以实现自己的attribute 写入class文件中, 供运行时使用。不同的attribute 通过attribute_name_index 来区分。

  • JVM 规范预定义的attribute

  • Code 结构

Code attribute 的作用是保存该放的的结构,如所对应的字节码

Code_attribute {u2 attribute_name_index;  // 00 09 ==> Codeu4 attribute_length; // 00 00 00 1D ==> 29u2 max_stack;   //  00 01 栈深度为 1 (栈帧中操作数栈的深度)u4 code_length;   // 00 00 00 05 指令的长度是多少u1 code[code_lenght]; // 2A B7 00 01 B1 其中 2A// 2A aload_0// B7 invokespecial// 00 #1// 01 <java/lang/Object.<init>>// B1 return// 0 aload_0// 1 invokespecial #1 <java/lang/Object.<init>>// 4 returnu2 exception_table_length; // 00 00 {u2 start_pc;u2 end_pc;u2 handler_pc;u2 catch_type;} exception_table[exception_table_lenght];u2 attributes_count; // 00 01attribute_info attributes[attributes_count];
}
  • attribute_length 表示 attribute 所包含的字节数, 不包含attribute_name_index 和 attribute_length 字段。

  • max_stack 表示这个方法运行的任何时刻所能达到的操作数栈的最大深度。

  • max_locals 表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量。

  • code_length 表示该方法所包含的字节码的字节数以及具体的指令码。

  • 具体字节码即是该方法被调用时,虚拟机所执行的字节码。

  • exception_table, 这里存放的是处理异常信息。

  • 每个 exception_table, 这里存放的是处理异常的信息。

  • 每个 exception_table 表项由start_pc , end_pc, handler_pc, catch_type 组成。

  • start_pc 和 end_pc 表示在code数组中的从 start_pc 到 end_pc 处(包含start_pc, 不包含end_pc)的指令抛出的异常会由 这个表项来处理。

  • handler_pc 表示处理异常的代码的开始处。catch_type 表示会被处理的异常类型, 它指向常量池里的一个异常类。当catch_type为0时, 表示处理所有的异常。

  • LineNumberTable 的结构

LineNumberTable_attribute {u2 attribute_name_index; //00 0A 常量池10号位置  LineNumberTableu4 attribute_lenght;  // 00 00 00 06 一共 6个长度u2 line_number_table_length;  // 00 01 映射的对数  1 对line_number_info {u2 start_pc; // 指令行号  00 00 u2 line_number; // 源码调试  00 03}line_number_table[line_number_table_length];
}

局部变量表 LocalVariableTable

LocalVariableTable_attribute {u2 attribute_name_index; u4 attribute_lenght;u2 local_variable_table_length; {u2 start_pc;u2 line_number;u2 name_index;u2 descriptor_index; u2 index;}
}

总结

  1. 构造方法中会初始化成员属性的默认值,如果自己实现了默认的构造方法, 依然还是在构造方法中赋值,这就是对指令的重排序。

  2. 如果多个构造方法那么每个构造方法中都有初始化成员变量的属性,来保障每个构造方法初始化的时候都会执行到属性的初始化过程。

  3. 如果构造方法中有执行语句, 那么会先执行赋值信息, 然后在执行自定义的执行代码。

  4. 对于Java每一个实例方法(非静态方法), 其在编译后生成的字节码中比实际方法多一个参数, 它位于方法的第一个参数位置. 我们就可以在当前方法中 的this去访问当前对象中的this这个操作是在Javac 编译器在编译期间将this的访问转换为对普通实例方法的参数访问,接下来在运行期间, 由JVM的调用实例方法时, 自动向实例方法中传入该this参数, 所以在实例方法的局部变量表中, 至少会一个指向当前对象的局部变量。

  5. 字节码对于处理异常的方式:

  • 统一采用异常表的方式来对异常处理。

  • 在Jdk1.4.2之前的版本中, 并不是使用异常表的方式来对异常进行处理的,而是采用特定的指令方式。

  • 当异常处理存在finally 语句块时,现代化的JVM才去的处理方式将finally语句块的自己拼接到每一个catch块后面, 换句话说,程序中中存在多个catch块,就会在每一个catch块后面重复多少个finally 的语句块字节码。

公众号:运维开发故事

github:https://github.com/orgs/sunsharing-note/dashboard

爱生活,爱运维

如果你觉得文章还不错,就请点击右上角选择发送给朋友或者转发到朋友圈。您的支持和鼓励是我最大的动力。喜欢就请关注我吧~

                                          ........................

JVM 字节码解析过程相关推荐

  1. 【JVM · 字节码】指令集 解析说明

    1. 概述 Java字节码指令对于虚拟机,就好像汇编语言对于计算机,属于基本执行指令. Java虚拟机的指令由 一个字节长度 的.代表着某种特定操作含义的数字(称为 操作码/Opcode)以及跟随其后 ...

  2. JVM与字节码——2进制流字节码解析

    为什么80%的码农都做不了架构师?>>>    字节码解析 结构 本位将详细介绍字节码的2进制结构和JVM解析2进制流的规范.规范对字节码有非常严格的结构要求,其结构可以用一个JSO ...

  3. java开发C语言编译器:把C实现的快速排序算法编译成jvm字节码

    有了前面一系列的铺垫和准备后,我们终于能走到至关重要的一刻.在本节,我们将用C语言开发快速排序算法,然后利用我们的编译器把它编译成java字节码,让C语言编写的快速排序算法能在java虚拟机上顺利执行 ...

  4. 深入理解JVM——字节码

    字节码 意义 字节码存在的意义就是解决Java跨平台问题,一次编写,到处执行.在不同的操作系统.不同硬件平台上,均可以不同修改代码即可顺畅地执行.作为Java与操作系统的中间码,成功解耦了语言对平台的 ...

  5. 【Java 虚拟机原理】动态字节码技术 | Dalvik ART 虚拟机 | Android 字节码打包过程

    文章目录 一.动态字节码技术 二.Dalvik & ART 虚拟机 三.Android 字节码打包过程 总结 一.动态字节码技术 动态字节码技术 就是在 运行时 , 动态修改 Class 字节 ...

  6. JVM字节码指令集大全及其介绍

    Java是怎么跨平台的 我们上计算机课的时候老师讲过:"计算机只能识别0和1,所以我们写的程序要经过编译器翻译成0和1组成的二进制格式计算机才能执行".我们编译后产生的.class ...

  7. Java指令全集_Java的JVM字节码指令集详解

    本文详细介绍了如何使用javap查看java方法中的字节码.以及各种字节码的含义,并且配以完善的案例,一步步,从头到尾带领大家翻译javap的输出.在文末还附有JVM字节码指令集表. 本文不适合没有J ...

  8. 2个字节能存多少个16进制_初探JVM字节码 - 疾风老头

    作者: LemonNan 原文地址: 代码地址: 字节码 概述 本篇要介绍的是能 "一次编译,到处运行的 JVM 字节码" 为什么能到处运行? 是因为在 任意平台下所编译出来的 c ...

  9. 深入理解JVM字节码(二)

    目录 字节码基础 一.字节码概述 二.Java虚拟机栈和栈帧 栈帧 1. 局部变量表 2. 操作数栈 三.字节码指令 1. 加载和存储指令 2. 操作数栈指令 3. 对象相关的字节码指令 1. ``方 ...

最新文章

  1. 数字图像处理:图像就是函数的解读
  2. 油管博主路透 3080Ti 参数、黄教主烤箱中拿出 DGX A100 预热发布会
  3. hdu2438 三分
  4. 夏末浅笑_2014年夏末大Java新闻
  5. c语言清空输入缓冲区函数,c语言:C语言清空输入缓冲区在标准输入(stdin)情况 -电脑资料...
  6. php怎么排除空的数组,【技术产品】php如何去除空数组
  7. c ++查找字符串_C ++朋友功能| 查找输出程序| 套装2
  8. iOS检测QQ是否安装
  9. Linux多线程实践(四 )线程的特定数据
  10. 转移印花技术基础知识及应用方法
  11. Rose软件安装教程
  12. 超赞!终于有网友用Java实现了第三方qq账号登录...
  13. 清华大学android源码下载网站地址
  14. 【华为OD机试真题 JS】解压报文
  15. 计算机专业英语复习第一天
  16. python控制电脑音量,声音之控制音量,,
  17. 树莓派-11-3又1/2位数字万用表使用说明书
  18. 不知道读什么?5种方法教你打造私房书单!
  19. 如何查看自己的数据库
  20. MongoDB $lookup函数实现两个表的关联查询+筛选+取特定值

热门文章

  1. Rust机器学习之ndarray
  2. EC03-DNC4G通信模块的使用
  3. c/c++ socket函数详解
  4. 眼底病php 是什么病,眼底病是什么原因引起的?
  5. SOUI自定义控件(2)
  6. 普华永道:尽管趋势看跌 对冲基金仍在涉足加密领域
  7. 论文笔记——Fair Resource Allocation in Federated Learning
  8. java中int与char的互相转换
  9. 航海王热血航线显示服务器,航海王热血航线游戏登录排队进不去解决方法
  10. springfox源码_springfox 源码分析(四) 配置类初始化