JVM 介绍

众所周知,Java 是运行在 Java 虚拟机上的,.java 文件只有编译成为 .class 文件才能在虚拟机上运行。那么这个虚拟机又是什么?

Oracle 发布了一般又一般的 Java 和 JVM 规范,虚拟机就是遵循这个规范运行。

规范的话全英文的,我是看不懂,有兴趣自己取:https://docs.oracle.com/javase/specs/index.html

既然 JVM 存在公开规范,那就说明,它不单单只支持 JAVA,并且,JVM 也不是唯一的。

我们平时认识最多的,应该是 HotSpot ,这是最广泛的 Java 虚拟机。比如查看 java 版本就能发现它

java version "11.0.8" 2020-07-14 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.8+10-LTS)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.8+10-LTS, mixed mode)

不同的代码在编译器下可以编译成相同的字节码,字节码也可以在不同的 JVM 上运行。

虚拟机的区别不会详细研究,都是遵循 JVM 规范,也没有多大区别,并且我们也不会去更改。


然后就是另一个角度,不同代码可以在同一个 JVM 上运行,如下图:

JVM、字节码、编译器的关系.png

虚拟机不关心代码,只要编译成的字节码符合规范,它就能运行。


Java 字节码文件

对 Java 而言,获得 .class 字节码文件需要通过 javac 编译器,因为在执行的时候,存在对字节码的第二次编译,所以 javac 编译器也成为前端编译。

先从 Hello World 开始。。

public class JvmTest1 {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

这么一段代码,编译后的 class 文件打开后这样的:

cafe babe 0000 0034 0022 0a00 0600 1409
0015 0016 0800 170a 0018 0019 0700 1a07
001b 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 124c 6f63
616c 5661 7269 6162 6c65 5461 626c 6501
0004 7468 6973 0100 0e4c 6a76 6d2f 4a76
6d54 6573 7431 3b01 0004 6d61 696e 0100
1628 5b4c 6a61 7661 2f6c 616e 672f 5374
7269 6e67 3b29 5601 0004 6172 6773 0100
135b 4c6a 6176 612f 6c61 6e67 2f53 7472
696e 673b 0100 0a53 6f75 7263 6546 696c
6501 000d 4a76 6d54 6573 7431 2e6a 6176
610c 0007 0008 0700 1c0c 001d 001e 0100
0c48 656c 6c6f 2057 6f72 6c64 2107 001f
0c00 2000 2101 000c 6a76 6d2f 4a76 6d54
6573 7431 0100 106a 6176 612f 6c61 6e67
2f4f 626a 6563 7401 0010 6a61 7661 2f6c
616e 672f 5379 7374 656d 0100 036f 7574
0100 154c 6a61 7661 2f69 6f2f 5072 696e
7453 7472 6561 6d3b 0100 136a 6176 612f
696f 2f50 7269 6e74 5374 7265 616d 0100
0770 7269 6e74 6c6e 0100 1528 4c6a 6176
612f 6c61 6e67 2f53 7472 696e 673b 2956
0021 0005 0006 0000 0000 0002 0001 0007
0008 0001 0009 0000 002f 0001 0001 0000
0005 2ab7 0001 b100 0000 0200 0a00 0000
0600 0100 0000 0600 0b00 0000 0c00 0100
0000 0500 0c00 0d00 0000 0900 0e00 0f00
0100 0900 0000 3700 0200 0100 0000 09b2
0002 1203 b600 04b1 0000 0002 000a 0000
000a 0002 0000 0009 0008 000a 000b 0000
000c 0001 0000 0009 0010 0011 0000 0001
0012 0000 0002 0013

字节码文件结构

字节码文件结构是一组以 8 位字节为基础的二进制流,各数据项目严格按照顺序紧凑地排列在 Class 文件之中,中间没有添加任何分隔符。

在字节码结构中,有两种最基本的数据类型来表示字节码文件格式,分别是:无符号数和表。

太复杂的东西我也没看懂,这里都是简单来说,大多数内容也就复制下。。。。

我们先来看一张表:

类型名称说明长度
u4magic魔数,识别 Class 文件格式4 个字节
u2minor_version副版本号2 个字节
u2major_version主版本号2 个字节
u2constant_pool_count常量池计算器2 个字节
cp_infoconstant_pool常量池n 个字节
u2access_flags访问标志2 个字节
u2this_class类索引2 个字节
u2super_class父类索引2 个字节
u2interfaces_count接口计数器2 个字节
u2interfaces接口索引集合2 个字节
u2fields_count字段个数2 个字节
field_infofields字段集合n 个字节
u2methods_count方法计数器2 个字节
method_infomethods方法集合n 个字节
u2attributes_count附加属性计数器2 个字节
attribute_infoattributes附加属性集合n 个字节

这是一张 Java 字节码总的结构表,我们按照上面的顺序逐一进行解读就可以了。

详细的就算了,看看主要结构,从大佬那扒来的图:

java-jvm-class-2


反编译字节码文件

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

比如我执行后输出:

Classfile /E:/HolyShit/test-demos/java-new/target/classes/jvm/JvmTest1.class
  Last modified 2020年9月22日; size 536 bytes
  MD5 checksum 95b43cc3e4240d26e3803d3a64eda12b
  Compiled from "JvmTest1.java"
public class jvm.JvmTest1
  minor version: 0
  major version: 52
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #5                          // jvm/JvmTest1
  super_class: #6                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
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            // Hello World!
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // jvm/JvmTest1
   #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               Ljvm/JvmTest1;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               JvmTest1.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               Hello World!
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               jvm/JvmTest1
  #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 jvm.JvmTest1();
    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 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljvm/JvmTest1;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) 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 Hello World!
         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: "JvmTest1.java"

看看就好了,太多知识性内容我也写不完。。。凑个字数


参考文章

https://www.cnblogs.com/chanshuyi/p/jvm_serial_02_the_history_of_jvm.html

https://www.pdai.tech/md/java/jvm/java-jvm-class.html

https://www.cnblogs.com/chanshuyi/p/jvm_serial_05_jvm_bytecode_analysis.html

https://blog.csdn.net/u011810352/article/details/80316870

https://blog.csdn.net/weelyy/article/details/78969412

https://www.jianshu.com/p/e713defb5afc

https://www.cnblogs.com/fx-blog/p/11982275.html