JVM之类文件结构

简介

类文件:又称字节码文件,java源代码编译之后产生的文件,是基于字节码的二进制文件,jvm通过运行类文件来执行java程序。

字节码:类文件中的基本数据单位,一个字节码占一个字节。

类文件的基本结构

类文件中存储的数据:类文件相当于jvm的指令集,文件中存储了两种数据,虚拟机指令和操作数

  • 虚拟机指令:因为一个字节可以表示256个数字,所以Java支持256个虚拟机指令,目前已使用200多个,虚拟机指令相当于CPU的操作码。
  • 操作数:虚拟机指令操作的数据,操作数是字节码指令的一部分,用于指定指令的具体操作对象,例如变量、常量或方法调用的目标。

类文件的基本结构:

text 复制代码
ClassFile {
    u4                    magic;                // 类文件的魔数,每个类型的文件都有自己的魔数
    u2                    minor_version;        // 次版本
    u2                    major_version;        // 主版本
    u2                    constant_pool_count;  // 常量池中数据项的个数
    cp_info               constant_pool;        // 常量池
    u2                    this_class;           // 当前类的全路径
    u2                    super_class;          // 当前类的父类的全路径
    u2                    interfaces_count;     // 接口数量
    u2                    interfaces;           // 接口的路径
    u2                    fields_count;         // 字段数量
    field_info            fields;               // 字段
    u2                    methods_count;        // 方法数量
    method_info           methods;              // 方法
    u2                    attributes_count;     // 属性数量
    attribute_info        attributes;           // 属性
}

内容讲解:u1、u2、u4、u8:分别代表1个字节、2个字节、4个字节、8个字节。在这里,u4、u2等内容,表示它对应的数据项占多少个字节,它们本身并不存在于文件中

类文件中的各数据项严格按照定义进行排列,中间没有分隔符和填充,如果遇到8位以上的数据项,按照高位在前的方式拆分成若干个字节存储

前置容量:类文件不采用分隔符的方式分隔数据,数据项的顺序是被严格限定的,因此当需要描述多个同类数据项的时候,采用前置容量计数器的方式。

查看类文件的十六进制形式

类文件是一种二进制文件,使用普通的文本编辑器只能看到乱码,使用十六进制的形式来查看,则可以看到原始的二进制的内容。

方法:

  • 在Linux平台下,使用xxd命令,得到十六进制形式的类文件
  • 使用vim名, 打开字节码文件,输入命令 :%!xxd

案例:字节码文件的内容是一个最简单的Hello World

text 复制代码
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 1c4c 6f72 672f 7779  ..this...Lorg/wy
00000070: 6a2f 6a76 6d2f 7031 302f 4865 6c6c 6f57  j/jvm/p10/HelloW
00000080: 6f72 6c64 3b01 0004 6d61 696e 0100 1628  orld;...main...(
00000090: 5b4c 6a61 7661 2f6c 616e 672f 5374 7269  [Ljava/lang/Stri
000000a0: 6e67 3b29 5601 0004 6172 6773 0100 135b  ng;)V...args...[
000000b0: 4c6a 6176 612f 6c61 6e67 2f53 7472 696e  Ljava/lang/Strin
000000c0: 673b 0100 0a53 6f75 7263 6546 696c 6501  g;...SourceFile.
000000d0: 000f 4865 6c6c 6f57 6f72 6c64 2e6a 6176  ..HelloWorld.jav
000000e0: 610c 0007 0008 0700 1c0c 001d 001e 0100  a...............
000000f0: 0a48 656c 6c6f 576f 726c 6407 001f 0c00  .HelloWorld.....
00000100: 2000 2101 001a 6f72 672f 7779 6a2f 6a76   .!...org/wyj/jv
00000110: 6d2f 7031 302f 4865 6c6c 6f57 6f72 6c64  m/p10/HelloWorld
00000120: 0100 106a 6176 612f 6c61 6e67 2f4f 626a  ...java/lang/Obj
00000130: 6563 7401 0010 6a61 7661 2f6c 616e 672f  ect...java/lang/
00000140: 5379 7374 656d 0100 036f 7574 0100 154c  System...out...L
00000150: 6a61 7661 2f69 6f2f 5072 696e 7453 7472  java/io/PrintStr
00000160: 6561 6d3b 0100 136a 6176 612f 696f 2f50  eam;...java/io/P
00000170: 7269 6e74 5374 7265 616d 0100 0770 7269  rintStream...pri
00000180: 6e74 6c6e 0100 1528 4c6a 6176 612f 6c61  ntln...(Ljava/la
00000190: 6e67 2f53 7472 696e 673b 2956 0021 0005  ng/String;)V.!..
000001a0: 0006 0000 0000 0002 0001 0007 0008 0001  ................
000001b0: 0009 0000 002f 0001 0001 0000 0005 2ab7  ...../........*.
000001c0: 0001 b100 0000 0200 0a00 0000 0600 0100  ................
000001d0: 0000 0600 0b00 0000 0c00 0100 0000 0500  ................
000001e0: 0c00 0d00 0000 0900 0e00 0f00 0100 0900  ................
000001f0: 0000 3700 0200 0100 0000 09b2 0002 1203  ..7.............
00000200: b600 04b1 0000 0002 000a 0000 000a 0002  ................
00000210: 0000 0008 0008 0009 000b 0000 000c 0001  ................
00000220: 0000 0009 0010 0011 0000 0001 0012 0000  ................
00000230: 0002 0013                                ....

结果解读:

  • 整体结构:除第一列外,每个字符代表4比特数据,因为每4比特数据组成一个十六进制字符,所以,每两个字符代表一个字节,一行有16个字节
  • 第一列:地址偏移量,它是十六进制的

部分解读十六进制内容:

  • magic:魔数,1-4 字节,cafe babe。所有的文件都有自己的特定类型,魔数用来表示一个文件究竟是什么文件。虚拟机在读取到魔数后认为该文件是一个字节码文件。

  • minor_version:次要版本,5-6 字节,0000,换算成十进制是0

  • major_version:主版本,7-8字节,0034,换算成十进制是52,代表jdk8。通过版本号,虚拟机能够检测是否可兼容该类文件

使用javap命令反编译源代码

案例:javap -v 字节码文件,字节码文件的内容是一个最简单的Hello World

打印结果:

text 复制代码
Classfile /Users/work/project/java/learn-jvm/out/production/jvm-learn/org/wyj/jvm/p10/HelloWorld.class
  Last modified 2024-3-10; size 564 bytes
  MD5 checksum cc6a38615b9ded3b89bb45327bb537e3
  Compiled from "HelloWorld.java"
public class org.wyj.jvm.p10.HelloWorld
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // HelloWorld
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // org/wyj/jvm/p10/HelloWorld
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lorg/wyj/jvm/p10/HelloWorld;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorld.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               HelloWorld
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               org/wyj/jvm/p10/HelloWorld
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public org.wyj.jvm.p10.HelloWorld();
    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 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lorg/wyj/jvm/p10/HelloWorld;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String HelloWorld
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 8: 0
        line 9: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

javap反编译结果解读:

  • 第一部分:Classfile,类文件的具体位置、上次修改时间、校验和、源文件位置

  • 第二部分:类的签名、版本号

  • 第三部分:Constant pool,常量池

  • 第四部分:类中的方法,方法中的内容:

    • stack:代表操作数栈的深度

    • locals:代表局部变量表的长度

    • args_size:方法的参数个数

    • 接下来是 字节码行号 、字节码指令 、操作数

    • LineNumberTable:源码的行号和字节码行号的对应关系

    • LocalVariableTable:局部变量表

      • start:字节码行号,在这一行,变量开始生效

      • length:长度,经过多少字节码行,变量的作用域结束

      • slot:变量的槽位,slot可以重复使用

      • name:变量的名称

      • Signature:变量的数据类型

数据类型的表示:基本数据类型使用一个特殊字符来表示,引用数据类型,在类的全路径前加 L,数组类型,如果是一维数组,加 [,如果是二维数组,加 [[。

类型的表示:

  • byte:B,字节
  • char:C,字符
  • double:D
  • float:F
  • int:I
  • long:J
  • short:S
  • boolean:Z
  • void:V
  • 类对象:L类的全路径
  • 一维数组:[类的全路径,例如,字符串数组 [Ljava/lang/String
  • 二维数组:[[类的全路径

<clinit>方法和<init>方法

clinit方法:静态代码在字节码指令中的名称。在类初始化时调用,只会被调用一次,用于初始化类。它会依据顺序来收集类中的静态代码块和有初始化语句的静态变量,在初始化类时执行

init方法:构造方法在字节码指令中的名称。每个构造方法在执行时,都会收集类中的普通代码块和有初始化语句的成员变量,然后把这些代码放在构造方法的开头执行,注意,代码块在构造方法之前执行

Q&A

a++ + ++a的执行过程

源代码:

java 复制代码
int a = 1;
int b = a++ + ++a - a--;  // b = 1 a = 2

总结:这个表达式的计算顺序是从左到右,结果是 1 + 3 - 3 = 1,a = 2,具体计算过程是:a先参与运算,然后++,所以是1 + ++a - a--,++a时,此时a是2,先加加,后参与运算,所以是1 + 3 - a--,a减减也类似,先参与运算,后减减,所以是 1 + 3 - 3 ,此时a减减后是2,表达式的值是1。

这是一道面试题,当时觉得没人会这么写代码,但是真的被面试到了,发现自己不会。这道题更加详细的原理需要结合字节码指令来分析,和局部变量表、操作数栈相关,a++是先把数据加载到操作数栈,然后再把加加结果写回到局部变量表,基本是这样,但大概原理是上面描述到的。

相关推荐
syt_biancheng29 分钟前
Day3算法训练(简写单词,dd爱框框,3-除2!)
开发语言·c++·算法·贪心算法
影子24011 小时前
oralce创建种子表,使用存储过程生成最大值sql,考虑并发,不考虑并发的脚本,plsql调试存储过程,java调用存储过程示例代码
java·数据库·sql
武子康1 小时前
Java-172 Neo4j 访问方式实战:嵌入式 vs 服务器(含 Java 示例与踩坑)
java·服务器·数据库·sql·spring·nosql·neo4j
864记忆1 小时前
Qt Network 模块中的函数详解
开发语言·网络·qt
864记忆1 小时前
Qt Sql 模块中的函数详解
开发语言·网络·qt
程序猿DD1 小时前
深入探索剖析 JVM 的启动过程
java
白露与泡影1 小时前
Spring Boot项目优化和JVM调优
jvm·spring boot·后端
是店小二呀1 小时前
五分钟理解Rust的核心概念:所有权Rust
开发语言·后端·rust
她说人狗殊途1 小时前
存储引擎MySQL
开发语言