程序员第一个(玩具)JVM

2023年 9月 16日 47.2k 0

程序员第一个(玩具)JVM

我们都知道Java程序要运行在JVM之上,我们除了面试时会了解下JVM的面试题,之外可能很少会去想JVM是如何工作的。在这篇文章中,我会尝试写一个玩具JVM来展示其背后的核心原理,希望激发你进一步学习的兴趣。

一个简单的目标

package me.kagami.myjvm;

public class Add {
    public static int add(int a, int b) {
        return a + b;
    }
}

首先使用javac Add.java编译后得到Add.class文件。该文件是JVM可以执行的二进制文件。接下来要做的事情就是正确地实现一个能够执行Add.class文件的JVM。

CLASS LOADER

JVM的工作之一是类加载,那么我们需要了解class文件的内容。

如果我们用hexdump插件以16进制视图打开Add.class文件,我们可以看到class文件的组织形式,但我们现在还完看不懂这个文件。没关系,本文将手把手介绍怎么阅读class文件。

image-20230816142336719

如果查看Java规格说明,那么我们可以学习到classFile的结构。可以看到classFile文件总是以4字节的magic数开头(CAFEBABE)然后是2+2的版本信息 ,以此类推。而cp_info,field_info,method_info,attribute_info会比较复杂,本文具体以cp_info详细说明,只要会看cp_info后,其他三个都一样。

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

我们以上文的Add.class举例

magic[CA FE BA BE] minor_version[00 00] major_version[00 34] constant_pool_count[00 15] 0A 00 03 00 12 07

常量池计数是00 150x15换成十进制是21,规格说明里有这么一句话:

The constant_pool table is indexed from 1 to constant_pool_count - 1.

说明常量的个数应该是21-1为20个。那么说明constant_pool_count后面有20个常量信息,那么我们来看看常量池是怎么排列的吧。

根据Java规则说明,cp_info的结构如下:

cp_info {
    u1 tag;
    u1 info[];
}

忽略info前面的u1,因为规格说明里有这么一句话,说明tag后面是可能有多个字节的:

Each tag byte must be followed by two or more bytes giving information about the specific constant.

我们以上文的Add.class举例,constant_pool_count后第一个tag是 0A

CA FE BA BE 00 00 00 34 constant_pool_count[00 15] 0A 00 03 00 12 07

根据Java规格说明,我们查看tag表类别

Constant Kind Tag
CONSTANT_Class 7
CONSTANT_Fieldref 9
CONSTANT_Methodref 10
CONSTANT_InterfaceMethodref 11
CONSTANT_String 8
CONSTANT_Integer 3
CONSTANT_Float 4
CONSTANT_Long 5
CONSTANT_Double 6
CONSTANT_NameAndType 12
CONSTANT_Utf8 1
CONSTANT_MethodHandle 15
CONSTANT_MethodType 16
CONSTANT_Dynamic 17
CONSTANT_InvokeDynamic 18
CONSTANT_Module 19
CONSTANT_Package 20

0A换成十进制是10,所以第一个常量应该是CONSTANT_Methodref类型,那么我们再根据Java规格说明查看CONSTANT_Methodref类型的格式为:

CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

所以我们继续看Add.class文件,class_index和name_and_type_index都是常量池的引用,也就是说,class_index指向的是常量池的第3常量,name_and_type_index指向的是第18(0x12为十进制18)常量。

CA FE BA BE 00 00 00 34 constant_pool_count[00 15] CONSTANT_Methodref_info[0A class_index(00 03) name_and_type_index(00 12)] 07

常量池的看法我想你应该能看懂了,那么我现在直接给出常量池的全部解析后的结果,我们直接看第3和第18常量是什么吧。以下是按照tag表分出来的20个常量:

CA FE BA BE 00 00 00 34 constant_pool_count[00 15] [0A 00 03 00 12] [0700 13] [07 00 14] CONSTANT_Utf8_info [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 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65] [01 00 04 74 68 69 73] [01 00 15 4C 6D 65 2F 6B 61 67 61 6D 69 2F 6D 79 6A 76 6D 2F 41 64 64 3B] [01 00 03 61 64 64] [01 00 05 28 49 49 29 49] [01 00 01 61] [01 00 01 49] [01 00 01 62] [01 00 0A 53 6F 75 72 63 65 46 69 6C 65] [01 00 08 41 64 64 2E 6A 61 76 61] [0C 00 04 00 05] [01 00 13 6D 65 2F 6B 61 67 61 6D 69 2F 6D 79 6A 76 6D 2F 41 64 64] [01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74] 00 21 00 02 00 03 00 00 00 00 00 02 00 01 00 
....

从中可以看出第3常量是[07 00 14]其中tag为CONSTANT_Class,其结构为:

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

说明name_index也是一个常量的引用,0x14指向的是第20常量,它是一个CONSTANT_Utf8_info常量,这种常量是utf8表示的字符串,结构是:

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

所以第20常量[01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74]的长度是0x0010,也就是16个字符,从dump列可以用看出这16个字符是:java/lang/Object,同理可得第18常量为:0x040x05()V

那么第1个常量连起来就是java/lang/Object()V,但这表示是什么意思呢 ?我们翻阅Java规格说明针对CONSTANT_Methodref_info找到了这么一句话:

If the name of the method in a CONSTANT_Methodref_info structure begins with a '

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论