JVM 字节码指令解析(上)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 概述本文主要是基于 .class 文件,进行分析 .class 文件的内容。这部分个人觉得主要是属于设计机构拓展的内容,大家可以一起来学习一下 Java 字节码的设计结构以及感受一下设计者的设计。

class 类文件结构


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


一个简单的 Java 代码


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


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


image.png


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


Classfile /../../TestClass.class
  Last modified 2021-2-6; size 306 bytes
  MD5 checksum eeba40cc40cc28ef4d416ff70d901561
  Compiled from "TestClass.java"
public class cn.edu.cqvie.jvm.bytecode.TestClass
  minor version: 0
  major version: 52
  flags: 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: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field m:I
         5: iconst_1
         6: iadd
         7: dup_x1
         8: putfield      #2                  // Field m:I
        11: ireturn
      LineNumberTable:
        line 8: 0
}
SourceFile: "TestClass.java"


Java 字节码结构


image.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开始。


image.png


上面表中描述了11种数据类型的机构, 其实在jdk1.7之后又增加了3种(CONSTANT_MethodHandle_info, CONSTANT_MethodType_info 以及CONSTANT_InvokeDynami_info)。这样一共14种。


  1. 在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;


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


` 3. 用描述符描述方法时, 按照先参数列表, 后返回值的顺序来描述. 参数列表按照参数的严格顺序放在一组()内, 如方法: 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 的位置。


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


  1. 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


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


相关文章
|
8天前
|
Java
轻松上手Java字节码编辑:IDEA插件VisualClassBytes全方位解析
本插件VisualClassBytes可修改class字节码,包括class信息、字段信息、内部类,常量池和方法等。
57 6
|
1月前
|
存储 SQL 小程序
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
这篇文章详细介绍了Java虚拟机(JVM)的运行时数据区域和JVM指令集,包括程序计数器、虚拟机栈、本地方法栈、直接内存、方法区和堆,以及栈帧的组成部分和执行流程。
31 2
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
|
1月前
|
SQL 缓存 Java
JVM知识体系学习三:class文件初始化过程、硬件层数据一致性(硬件层)、缓存行、指令乱序执行问题、如何保证不乱序(volatile等)
这篇文章详细介绍了JVM中类文件的初始化过程、硬件层面的数据一致性问题、缓存行和伪共享、指令乱序执行问题,以及如何通过`volatile`关键字和`synchronized`关键字来保证数据的有序性和可见性。
29 3
|
1月前
|
存储 安全 Java
JVM锁的膨胀过程与锁内存变化解析
在Java虚拟机(JVM)中,锁机制是确保多线程环境下数据一致性和线程安全的重要手段。随着线程对共享资源的竞争程度不同,JVM中的锁会经历从低级到高级的膨胀过程,以适应不同的并发场景。本文将深入探讨JVM锁的膨胀过程,以及锁在内存中的变化。
40 1
|
1月前
|
JSON JavaScript 前端开发
Javaweb中Vue指令的详细解析与应用
Vue指令提供了一种高效、声明式的编码方式,使得开发者可以更专注于数据和业务逻辑,而不是DOM操作的细节。通过熟练使用Vue指令,可以极大地提高开发效率和项目的可维护性。
20 3
|
2月前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
112 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
2月前
|
JavaScript 前端开发 UED
Javaweb中Vue指令的详细解析与应用
Vue指令是Vue框架中非常强大的特性之一,它提供了一种简洁、高效的方式来增强HTML元素和组件的功能。通过合理使用这些指令,可以使你的JavaWeb应用更加响应用户的操作,提高交互性和用户体验。而且,通过创建自定义指令,你可以进一步扩展Vue的功能,使其更贴合你的应用需求。
20 1
|
2月前
|
安全 前端开发 Java
浅析JVM invokedynamic指令与Java Lambda语法的深度融合
在Java的演进历程中,Lambda表达式无疑是Java 8引入的一项革命性特性,它极大地简化了函数式编程在Java中的应用,使得代码更加简洁、易于阅读和维护。而这一切的背后,JVM的invokedynamic指令功不可没。本文将深入探讨invokedynamic指令的工作原理及其与Java Lambda语法的紧密联系,带您领略这一技术背后的奥秘。
28 1
|
3月前
|
C# 开发者 Windows
震撼发布:全面解析WPF中的打印功能——从基础设置到高级定制,带你一步步实现直接打印文档的完整流程,让你的WPF应用程序瞬间升级,掌握这一技能,轻松应对各种打印需求,彻底告别打印难题!
【8月更文挑战第31天】打印功能在许多WPF应用中不可或缺,尤其在需要生成纸质文档时。WPF提供了强大的打印支持,通过`PrintDialog`等类简化了打印集成。本文将详细介绍如何在WPF应用中实现直接打印文档的功能,并通过具体示例代码展示其实现过程。
303 0
|
3月前
|
缓存 前端开发 Java
浅析JVM invokedynamic指令与Java Lambda语法
【8月更文挑战第27天】在Java的演进历程中,invokedynamic指令的引入和Lambda表达式的出现无疑是两大重要里程碑。它们不仅深刻改变了Java的开发模式和性能表现,还极大地推动了Java在函数式编程和动态语言支持方面的进步。本文将从技术角度浅析JVM中的invokedynamic指令及其与Java Lambda语法的紧密联系。
55 0

推荐镜像

更多