目 录CONTENT

文章目录

[JVM学习] 如何阅读Class文件

Sir丶雨轩
2022-06-06 / 0 评论 / 0 点赞 / 1287 阅读 / 0 字 / 正在检测是否收录...

本文使用Java版本 1.8.0_202

JVM版本 HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)

参考书籍:《Java虚拟机规范.Java SE 8版》

首先我们定义一个源文件

package com.yuxuan66.jvm.classdef;

/**
 * @author Sir丶雨轩
 * @since 2022/6/6
 */
public class Hello {

    public static String msg = "World";

    public static void main(String[] args) {
        System.out.println("Hello " + msg);
    }
}

然后我们运行得到编译后的Class文件,通过javap把Class文件还原为Java编译器生成的字节码

javap -verbose ./Hello.class

-verbose 输出栈大小,方法参数的个数

我们会得到如下的字节码

Classfile /F:/Developer/Java/JVMStudy/out/production/JVMStudy/com/yuxuan66/jvm/classdef/Hello.class
  Last modified 2022年6月6日; size 840 bytes
  SHA-256 checksum ab48ccf6fcf0d882a5a0e6825526ba8c890efeea38631f71bcfe2d844216d63f
  Compiled from "Hello.java"
public class com.yuxuan66.jvm.classdef.Hello
  minor version: 0
  major version: 52
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #11                         // com/yuxuan66/jvm/classdef/Hello
  super_class: #12                        // java/lang/Object
  interfaces: 0, fields: 1, methods: 3, attributes: 1
Constant pool:
   #1 = Methodref          #12.#29        // java/lang/Object."<init>":()V
   #2 = Fieldref           #30.#31        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Class              #32            // java/lang/StringBuilder
   #4 = Methodref          #3.#29         // java/lang/StringBuilder."<init>":()V
   #5 = String             #33            // Hello
   #6 = Methodref          #3.#34         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #7 = Fieldref           #11.#35        // com/yuxuan66/jvm/classdef/Hello.msg:Ljava/lang/String;
   #8 = Methodref          #3.#36         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Methodref          #37.#38        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #10 = String             #39            // World
  #11 = Class              #40            // com/yuxuan66/jvm/classdef/Hello
  #12 = Class              #41            // java/lang/Object
  #13 = Utf8               msg
  #14 = Utf8               Ljava/lang/String;
  #15 = Utf8               <init>
  #16 = Utf8               ()V
  #17 = Utf8               Code
  #18 = Utf8               LineNumberTable
  #19 = Utf8               LocalVariableTable
  #20 = Utf8               this
  #21 = Utf8               Lcom/yuxuan66/jvm/classdef/Hello;
  #22 = Utf8               main
  #23 = Utf8               ([Ljava/lang/String;)V
  #24 = Utf8               args
  #25 = Utf8               [Ljava/lang/String;
  #26 = Utf8               <clinit>
  #27 = Utf8               SourceFile
  #28 = Utf8               Hello.java
  #29 = NameAndType        #15:#16        // "<init>":()V
  #30 = Class              #42            // java/lang/System
  #31 = NameAndType        #43:#44        // out:Ljava/io/PrintStream;
  #32 = Utf8               java/lang/StringBuilder
  #33 = Utf8               Hello
  #34 = NameAndType        #45:#46        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #35 = NameAndType        #13:#14        // msg:Ljava/lang/String;
  #36 = NameAndType        #47:#48        // toString:()Ljava/lang/String;
  #37 = Class              #49            // java/io/PrintStream
  #38 = NameAndType        #50:#51        // println:(Ljava/lang/String;)V
  #39 = Utf8               World
  #40 = Utf8               com/yuxuan66/jvm/classdef/Hello
  #41 = Utf8               java/lang/Object
  #42 = Utf8               java/lang/System
  #43 = Utf8               out
  #44 = Utf8               Ljava/io/PrintStream;
  #45 = Utf8               append
  #46 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #47 = Utf8               toString
  #48 = Utf8               ()Ljava/lang/String;
  #49 = Utf8               java/io/PrintStream
  #50 = Utf8               println
  #51 = Utf8               (Ljava/lang/String;)V
{
  public static java.lang.String msg;
    descriptor: Ljava/lang/String;
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC

  public com.yuxuan66.jvm.classdef.Hello();
    descriptor: ()V
    flags: (0x0001) 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 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/yuxuan66/jvm/classdef/Hello;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #3                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
        10: ldc           #5                  // String Hello
        12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        15: getstatic     #7                  // Field msg:Ljava/lang/String;
        18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        21: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        24: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        27: return
      LineNumberTable:
        line 12: 0
        line 13: 27
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      28     0  args   [Ljava/lang/String;

  static {};
    descriptor: ()V
    flags: (0x0008) ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #10                 // String World
         2: putstatic     #7                  // Field msg:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 9: 0
}
SourceFile: "Hello.java"

我们这里使用Notepad++来阅读Class文件的十六进制代码,需要安装插件Hex-Editer
首先我们来看一下Class文件的结构

这个u?就代表字节数
首先第一个u4 = 魔数
魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的Class文件。魔数值固定0xCAFEBABE,不会改变

接下来两个u2分别代表副版本号和主版本号

我们可以看到副版本号为 00 00 则值为0
主版本号为 00 34 这里是十六进制,我们转换为10进制 3 * 16 + 4 = 52
我们可以在下面这张表上面看到52=Java 8

现在我们进行下一个u2 constan_pool_count 这个代表常量池的数量

00 34 转换为10进制,我们知道一共有52个常量,计算方法可以参考上面版本号或者百度进制转换

在JVM规范中如果常量池的数量不为0,这里一般都是不为0的。下面的数据为常量池的具体数据,简称cp_info
我们可以在JVM规范中找到cp_info的定义


常量池tag的含义

我们得知接下来的u1为常量的标记


0a转换10进制 = 10,这里代表CONSTANT_Methodref 我们需要从JVM规范中找到对应的解释

我们得知接下来的u1为方法的类型,u2为类的索引,u2为名称和类型的索引

这些索引是指在常量池中的索引,由于我们还没有分析完,所以我们可以对照上面javap给出的结果来查看


u1 tag; = 0a = 10
u2 class_index; = 0c = 12

 #12 = Class              #41            // java/lang/Object
 #41 = Utf8               java/lang/Object

我们可以看到这个类的全部类名
u2 name_and_type_index; = 1d = 29

 #29 = NameAndType        #15:#16        // "<init>":()V
 #15 = Utf8               <init>
 #16 = Utf8               ()V

我们可以看到这个方法叫 没有参数和返回值

<init> <cinit> 是JVM为我们生成的特殊方法

我们接着往下看

09 根据上面的数据我们可以得知这是一个字段的定义,CONSTANT_Fieldref
然后我们去JVM规范中找到他的描述

我们看到这个是跟方法的定义很像的

u1 tag; = 09 = 9
u2 class_index; = 1e = 30

 #30 = Class              #42            // java/lang/System
 #42 = Utf8               java/lang/System

u2 name_and_type_index; = 1f = 31

 #31 = NameAndType        #43:#44        // out:Ljava/io/PrintStream;
 #43 = Utf8               out
 #44 = Utf8               Ljava/io/PrintStream;

通过以上的数据我们可以看到是System里面的out字段 字段类型为 java/io/PrintStream

依次往后只需要遵循JVM规范即可阅读整个Class文件

0
广告 广告

评论区