java虚拟机原理:Class字节码二进制文件分析

时间:2021-08-23 16:59:18

一、字节码文件 与 JVM

Java 源码编译成 Class 字节码 ;

Java 虚拟机 可以被认为是一个 解释器 , 解释编译后的 Class 字节码文件 , 最后在不同的操作系统中运行 ;

Android 虚拟机 不是 Java 规范的 虚拟机 , 有一些根据嵌入式设备进行的定制的实现 ;

Class 字节码 本质上就是 二进制数据 , 运行时 , 会被 类加载器 加载到 Java 虚拟机内存的 方法区 中 ; 同时 创建 Class 对象 ;

( Java 虚拟机内存分为 : 堆区 , 方法区 , 栈 , 本地方法栈 , 程序计数器 )

由于要将 Class 字节码文件 加载到 JVM 内存的 方法区 中 , 要占用一定的内存空间 , 这里要求 Class 字节码文件 , 越小越好 ;

 

二、字节码文件示例

Java 源代码如下 :

public class Student {
  private String name;

  public String getName() {
      return name;
  }

  public void setName(String name) {
      this.name = name;
  }
}

使用 javac 命令将 Student.java 源码编译成 Student.class字节码文件 :

javac Student.java

字节码文件二进制数据分析 :

使用二进制查看工具查看 Student.class 字节码文件 , 这些二进制数值对应的就是 JVM 指令 ;

java虚拟机原理:Class字节码二进制文件分析

CA FE BA BE 00 00 00 34 00 15 0A 00 04 00 11 09
00 03 00 12 07 00 13 07 00 14 01 00 04 6E 61 6D
65 01 00 12 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53
74 72 69 6E 67 3B 01 00 06 3C 69 6E 69 74 3E 01
00 03 28 29 56 01 00 04 43 6F 64 65 01 00 0F 4C
69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 01 00
07 67 65 74 4E 61 6D 65 01 00 14 28 29 4C 6A 61
76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 01
00 07 73 65 74 4E 61 6D 65 01 00 15 28 4C 6A 61
76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29
56 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00
0C 53 74 75 64 65 6E 74 2E 6A 61 76 61 0C 00 07
00 08 0C 00 05 00 06 01 00 07 53 74 75 64 65 6E
74 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62
6A 65 63 74 00 21 00 03 00 04 00 00 00 01 00 02
00 05 00 06 00 00 00 03 00 01 00 07 00 08 00 01
00 09 00 00 00 1D 00 01 00 01 00 00 00 05 2A B7
00 01 B1 00 00 00 01 00 0A 00 00 00 06 00 01 00
00 00 01 00 01 00 0B 00 0C 00 01 00 09 00 00 00
1D 00 01 00 01 00 00 00 05 2A B4 00 02 B0 00 00
00 01 00 0A 00 00 00 06 00 01 00 00 00 05 00 01
00 0D 00 0E 00 01 00 09 00 00 00 22 00 02 00 02
00 00 00 06 2A 2B B5 00 02 B1 00 00 00 01 00 0A
00 00 00 0A 00 02 00 00 00 09 00 05 00 0A 00 01
00 0F 00 00 00 02 00 10

使用

javap -v Student.class

命令 , 生成上述字节码文件的 附加信息 ;

命令行输出 :

D:\jvm>javap -v Student.class
Classfile /D:/jvm/Student.class
Last modified 2021-9-4; size 392 bytes
MD5 checksum 8b9bb897bb8cf2a8addf04be5b7b915f
Compiled from "Student.java"
public class Student
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
 #1 = Methodref          #4.#17         // java/lang/Object."<init>":()V
 #2 = Fieldref           #3.#18         // Student.name:Ljava/lang/String;
 #3 = Class              #19            // Student
 #4 = Class              #20            // java/lang/Object
 #5 = Utf8               name
 #6 = Utf8               Ljava/lang/String;
 #7 = Utf8               <init>
 #8 = Utf8               ()V
 #9 = Utf8               Code
#10 = Utf8               LineNumberTable
#11 = Utf8               getName
#12 = Utf8               ()Ljava/lang/String;
#13 = Utf8               setName
#14 = Utf8               (Ljava/lang/String;)V
#15 = Utf8               SourceFile
#16 = Utf8               Student.java
#17 = NameAndType        #7:#8          // "<init>":()V
#18 = NameAndType        #5:#6          // name:Ljava/lang/String;
#19 = Utf8               Student
#20 = Utf8               java/lang/Object
{
public Student();
  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 java.lang.String getName();
  descriptor: ()Ljava/lang/String;
  flags: ACC_PUBLIC
  Code:
    stack=1, locals=1, args_size=1
       0: aload_0
       1: getfield      #2                  // Field name:Ljava/lang/String;
       4: areturn
    LineNumberTable:
      line 5: 0
public void setName(java.lang.String);
  descriptor: (Ljava/lang/String;)V
  flags: ACC_PUBLIC
  Code:
    stack=2, locals=2, args_size=2
       0: aload_0
       1: aload_1
       2: putfield      #2                  // Field name:Ljava/lang/String;
       5: return
    LineNumberTable:
      line 9: 0
      line 10: 5
}
SourceFile: "Student.java"

java虚拟机原理:Class字节码二进制文件分析

下面开始逐个字节解析上述字节码文件 ;

 

三、字节码文件二进制结构分析

分析字节码二进制文件时 , 可以参考 javap -v Student.class 命令输出的字节码附加信息进行理解 ;

1、魔数

magic ( 魔数 ) : 4 4 4 字节 , CA FE BA BE , 所有的 Class 字节码都是以 CafeBabe 信息开头的 ;

java虚拟机原理:Class字节码二进制文件分析

2、次版本号

minor_version ( 次版本号 ) : 2 2 2 字节 , 00 00 , 次版本号是 0 0 0 ; 对应字节码附加信息中的 minor version: 0 ;

java虚拟机原理:Class字节码二进制文件分析

3、主版本号

major_version ( 主版本号 ) : 2 2 2 字节 , 00 34 , 主版本号是 52 52 52 ; 对应字节码附加信息中的 major version: 52 ;

  • 这个主版本号 52 对应 JDK 版本的 1.8 版本 ;
  • 51 对应 1.7 ;
  • 53 对应 1.9 ;
  • 45 对应 1.0 ;

java虚拟机原理:Class字节码二进制文件分析

4、常量池个数

constant_pool_count ( 常量池个数 ) : 2 2 2 字节 , 00 15 , 常量池个数是 21 21 21 个 ; 由于 JVM 占用了默认的常量池 #0 , 因此实际上的常量个数是 21 − 1 21 - 1 21−1 个 , 需要对这个数减一处理 ;

java虚拟机原理:Class字节码二进制文件分析

字节码附加信息中 常量池参考 , 有 20 20 20 个常量池 ; #0 常量池 , 被 JVM 占用了 , 代表了一个空引用 , 不指向任何位置 ;

Constant pool:
 #1 = Methodref          #4.#17         // java/lang/Object."<init>":()V
 #2 = Fieldref           #3.#18         // Student.name:Ljava/lang/String;
 #3 = Class              #19            // Student
 #4 = Class              #20            // java/lang/Object
 #5 = Utf8               name
 #6 = Utf8               Ljava/lang/String;
 #7 = Utf8               <init>
 #8 = Utf8               ()V
 #9 = Utf8               Code
#10 = Utf8               LineNumberTable
#11 = Utf8               getName
#12 = Utf8               ()Ljava/lang/String;
#13 = Utf8               setName
#14 = Utf8               (Ljava/lang/String;)V
#15 = Utf8               SourceFile
#16 = Utf8               Student.java
#17 = NameAndType        #7:#8          // "<init>":()V
#18 = NameAndType        #5:#6          // name:Ljava/lang/String;
#19 = Utf8               Student
#20 = Utf8               java/lang/Object

 

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注服务器之家的更多内容!

原文链接:https://hanshuliang.blog.csdn.net/article/details/120104916