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++是先把数据加载到操作数栈,然后再把加加结果写回到局部变量表,基本是这样,但大概原理是上面描述到的。

相关推荐
龙雨LongYu128 分钟前
Go执行当前package下的所有方法
开发语言·后端·golang
米饭好好吃.15 分钟前
【Go】Go MongoDB 快速入门
开发语言·mongodb·golang
By北阳18 分钟前
Go语言 vs Java语言:核心差异与适用场景解析
java·开发语言·golang
J总裁的小芒果27 分钟前
java项目发送短信--腾讯云
java·python·腾讯云
wenbin_java33 分钟前
设计模式之桥接模式:原理、实现与应用
java·设计模式·桥接模式
孫治AllenSun34 分钟前
【Synchronized】不同的使用场景和案例
java·开发语言·jvm
ramsey171 小时前
Jmeter-RSA加密、解密、加签、验签
java·开发语言·python
程序视点2 小时前
重磅消息!Eclipse正式上线GitHub Copilot!
java·后端·github copilot
qq_8372873962 小时前
【Mac 系统卸载 Go 语言完整指南】
开发语言·macos·golang
程序员清风2 小时前
ZooKeeper是多主多从的结构,还是一主多从的结构?
java·后端·面试