JVM基础 - 类字节码详解

源代码通过编译器编译成字节码,再通过类加载子系统进行加载到JVM中运行

JVM是Java虚拟机,Java是高级语言,只有人类才能理解其逻辑,因此计算机不能直接识别Java源代码,所以Java代码需要通过编译器编译成class文件,最后通过jvm对字节码文件进行加载运行。

img

Java字节码文件

class文件本质上是一个以8为字节为基础单位的二进制文件。

jvm根据其特定的规则解析该二进制文件,从而得到相关信息。

Class文件采用一种伪结构来存储数据,它有两种类型:无符号数和表

Class文件的结构属性

在理解之前先整天看下java字节码文件包含了哪些类型的数据:

img

从一个例子开始

下面以一个简单的例子来逐步讲解字节码

// Main.java
public class Main{
   private int m;
    
   public int inc(){
       return m + 1;
   }
}

通过一下命令,可以在当前所在路径下生成一个Main.class文件。

javac Main.java

以文本的形式打开生成的class文件,内容如下:

0000000: cafe babe 0000 0034 0013 0a00 0400 0f09  .......4........
0000010: 0003 0010 0700 1107 0012 0100 016d 0100  .............m..
0000020: 0149 0100 063c 696e 6974 3e01 0003 2829  .I...<init>...()
0000030: 5601 0004 436f 6465 0100 0f4c 696e 654e  V...Code...LineN
0000040: 756d 6265 7254 6162 6c65 0100 0369 6e63  umberTable...inc
0000050: 0100 0328 2949 0100 0a53 6f75 7263 6546  ...()I...SourceF
0000060: 696c 6501 0009 4d61 696e 2e6a 6176 610c  ile...Main.java.
0000070: 0007 0008 0c00 0500 0601 0004 4d61 696e  ............Main
0000080: 0100 106a 6176 612f 6c61 6e67 2f4f 626a  ...java/lang/Obj
0000090: 6563 7400 2100 0300 0400 0000 0100 0200  ect.!...........
00000a0: 0500 0600 0000 0200 0100 0700 0800 0100  ................
00000b0: 0900 0000 1d00 0100 0100 0000 052a b700  .............*..
00000c0: 01b1 0000 0001 000a 0000 0006 0001 0000  ................
00000d0: 0001 0001 000b 000c 0001 0009 0000 001f  ................
00000e0: 0002 0001 0000 0007 2ab4 0002 0460 ac00  ........*....`..
00000f0: 0000 0100 0a00 0000 0600 0100 0000 0600  ................
0000100: 0100 0d00 0000 0200 0e0a                 ..........
  • 文件开头的4个字节("cafe babe")称之为魔术,唯有"cafe babe"开头的class文件才能被虚拟机接收,这4个字节就是字节码文件的声部识别。
  • 0000似乎编译器jdk版本的此版本号0,0034转换为十进制是52,是主版本号,java版本号从45开始,除1.0和1.1都是使用45.x外,以后每升一个大版本,版本号加一。也就是说,编译生成该class文件的jdk版本为1.8.0

通过java -version命令验证,可得结果。

Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)

反编译字节码文件

使用到java内置的一个反编译工具javap可以反编译字节码文件,用法:javap <options> <classes>

其中<options>选项包括:

  -help  --help  -?        输出此用法消息
  -version                 版本信息
  -v  -verbose             输出附加信息
  -l                       输出行号和本地变量表
  -public                  仅显示公共类和成员
  -protected               显示受保护的/公共类和成员
  -package                 显示程序包/受保护的/公共类
                           和成员 (默认)
  -p  -private             显示所有类和成员
  -c                       对代码进行反汇编
  -s                       输出内部类型签名
  -sysinfo                 显示正在处理的类的
                           系统信息 (路径, 大小, 日期, MD5 散列)
  -constants               显示最终常量
  -classpath <path>        指定查找用户类文件的位置
  -cp <path>               指定查找用户类文件的位置
  -bootclasspath <path>    覆盖引导类文件的位置

输入命令javap -verbose -p Main.class查看输出内容:

Classfile /C:/Users/THF/Desktop/Main.class
  Last modified 2023-2-14; size 265 bytes
  MD5 checksum 1f1569bd4f1e84377ce2a419c1ec032d
  Compiled from "Main.java"
public class Main
  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         // Main.m:I
   #3 = Class              #17            // Main
   #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               Main.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = NameAndType        #5:#6          // m:I
  #17 = Utf8               Main
  #18 = Utf8               java/lang/Object
{
  private int m;
    descriptor: I
    flags: ACC_PRIVATE

  public Main();
    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 1: 0

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 6: 0
}
SourceFile: "Main.java"
最后修改:2023 年 04 月 04 日
如果觉得我的文章对你有用,请随意赞赏