{"id":20673015,"url":"https://github.com/relph1119/jvmbypython","last_synced_at":"2025-04-19T19:11:00.081Z","repository":{"id":37696051,"uuid":"153995055","full_name":"Relph1119/JVMByPython","owner":"Relph1119","description":"《自己动手写Java虚拟机》JVM的python实现","archived":false,"fork":false,"pushed_at":"2020-08-22T08:35:43.000Z","size":8777,"stargazers_count":136,"open_issues_count":0,"forks_count":29,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-29T12:05:07.191Z","etag":null,"topics":["java","jvm","python-jvm"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Relph1119.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-10-21T10:10:54.000Z","updated_at":"2025-02-16T06:02:48.000Z","dependencies_parsed_at":"2022-09-12T14:23:29.419Z","dependency_job_id":null,"html_url":"https://github.com/Relph1119/JVMByPython","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Relph1119%2FJVMByPython","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Relph1119%2FJVMByPython/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Relph1119%2FJVMByPython/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Relph1119%2FJVMByPython/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Relph1119","download_url":"https://codeload.github.com/Relph1119/JVMByPython/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249773393,"owners_count":21323457,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","jvm","python-jvm"],"created_at":"2024-11-16T20:39:48.186Z","updated_at":"2025-04-19T19:11:00.062Z","avatar_url":"https://github.com/Relph1119.png","language":"Python","readme":"# 记录自己用Python完成编写JVM的过程\n\n项目完全参考张秀宏的《自己动手写Java虚拟机》代码结构，在此向本书作者表示感谢。\n\n## 运行环境\nPython 版本：3.7.2  \nPyCharm 版本：PyCharm 2018.3.7 (Professional Edition)  \nJava版本：1.8\n\n## 代码结构\n\u003cpre\u003e\nimages---------------------------------运行截图\njava-----------------------------------java的代码与class文件\n+----class-----------------------------java编译生成的class文件\n+----code------------------------------java的测试代码\nsrc------------------------------------jvm代码\n+-----ch01-----------------------------对应书中第1章实现代码\n+-----ch02-----------------------------对应书中第2章实现代码\n+-----ch03-----------------------------对应书中第3章实现代码\n+-----ch04-----------------------------对应书中第4章实现代码\n+-----ch05-----------------------------对应书中第5章实现代码\n+-----ch06-----------------------------对应书中第6章实现代码\n+-----ch07-----------------------------对应书中第7章实现代码\n+-----ch08-----------------------------对应书中第8章实现代码\n+-----ch09-----------------------------对应书中第9章实现代码\n+-----ch10-----------------------------对应书中第10章实现代码\n+-----develop_code---------------------持续开发的实现代码\n      +-----classfile------------------class文件解析的对象类\n      +-----classpath------------------类路径目录\n      +-----instructions---------------指令集\n      +-----native---------------------本地方法目录\n      +-----rtda-----------------------运行时数据区\n\u003c/pre\u003e\n\n**注意：** 将src和develop_code设置成sources Root，可避免代码的引包报错。\n\n## 代码编写与运行结果\n项目的所有运行都是采用直接运行Main.py的方式，请读者运行时注意。\n\n### 第1章-命令行工具\n完成一个简易的命令行工具，使用各种参数执行JVM命令  \n传入参数：\n\u003e --cp foo/bar MyApp arg1 arg2\n\n![](images/ch01/命令行工具.png)\n1. 采用OptionParser作为命令行解析器，具体处理的打印输入留给Cmd类去处理。\n2. 在使用命令行解析器时，必须使用\"--X\"表示参数，不能按照书中的\"-\"进行编码。\n\n### 第2章-搜索class文件\n完成搜索class文件功能，类路径的查找，按照搜索的先后顺序，类路径可以从以下3个部分查找：启动类路径、扩展类路径、用户类路径。\n传入参数：\n\u003e --Xjre \"D:\\JavaTools\\jdk1.8.0_151\\jre\" java.lang.Object\n\n![](images/ch02/搜索class文件.png)\n1. pathListSeparator引用路径写死为分号“;”，Linux下面为冒号。\n2. 由于class是Python的关键字，所有代码中的class改为了class_name。\n3. 如果该结构体是数组，由于Python无法表示结构数组，故类初始化的时候初始一个数组。\n\n### 第3章-解析class文件\n完成解析class文件功能，将class文件加载之后，按照JVM规范，读取字节，存储class的版本号，类属性、方法、接口的对象。  \n传入参数：  \n\u003e --Xjre \"D:\\JavaTools\\jdk1.8.0_151\\jre\" java.lang.String\n\n![](images/ch03/解析class文件.png)\n1. 采用property注解，将一些方法设置为属性，以方便对象属性调用；\n2. 对于一些要使用len()函数的对象，添加了相关的内置函数__len__()实现。  \n\n### 第4章-运行时数据区\n\u0026emsp;\u0026emsp;实现运行时数据区（run-time data area），可分为两类：一类是多线程共享的，另一类是线程私有的。多线程共享的运行时数据区需要在Java虚拟机启动时创建好，在Java虚拟机退出时销毁。线程私有的运行时数据区则在创建线程时才创建，线程退出时销毁。  \n\u0026emsp;\u0026emsp;多现场共享的内存区域主要存放两类数据：类数据和类实例（也就是对象）。对象数据存放在堆中，类数据存放在方法区中。线程私有的运行时数据区用于辅助执行Java字节码。  \n![](images/ch04/运行时数据区.png)\n\n### 第5章-指令集和解释器\n\u0026emsp;\u0026emsp;在前两章的基础上编写了一个简单的解释器，并实现大约150条指令，可以执行100个整数求和的程序，能得到5050的正确答案。  \n![](images/ch05/解析GaussTest的class文件.png)\n\n### 第6章-类和对象\n\u0026emsp;\u0026emsp;实现线程共享的运行时数据区，包括方法区、运行时常量池、类和对象、一个简单的类加载器，以及ldc和部分引用类指令。  \n![](images/ch06/类加载器的实现.png)\n\n### 第7章-方法调用和返回\n\u0026emsp;\u0026emsp;基本完成了方法调用和返回，并实现了类初始化逻辑，已经可以运行Fibonacci程序（求第30个Fibonacci数）。  \n![](images/ch07/解析Fibonacci程序.png)\n1. 程序需要运行好长时间，毕竟采用的是递归\n2. 目前有3个todo没有处理，其中没有采用深拷贝，不知道之后的程序会有什么问题。\n\n### 第8章-数组和字符串\n实现了数组和字符串的加载，终于可以运行HelloWorld程序了。\n1. 解析并执行BubbleSortTest（冒泡排序）算法\n![](images/ch08/解析并执行BubbleSortTest（冒泡排序）算法.png)\n2. 解析并执行HelloWorld程序，打印出Hello world!\n![](images/ch08/解析并执行HelloWorld程序.png)\n3. 解析并执行PrintArgs程序，打印出传入的参数\n![](images/ch08/解析并执行PrintArgs程序.png)\n\n**本章总结：**  \n1. 排查了冒泡排序算法执行时的问题，由于DUP指令的实现问题，之前采用的是slot的深拷贝，导致在对象引用置空的时候，不能将slot里的引用同时置空，解决方法：自己实现了一个copy_slot方法，创建一个Slot对象将num和ref都进行复制即可。\n2. 在测试HelloWorld程序时，解析java.lang.CharSequence类报错，最后查看到是由于ConstantMethodHandleInfo类中的read_info读取问题导致的。\n\n### 第9章-本地方法调用\n实现了本地方法调用的指令，以及Java类库中一些最基本的类和本地方法，有如下本地方法：java.lang.Object.getClass()、java.lang.Class.getPrimitiveClass()、java.lang.Class.getName0()、java.lang.Class.desiredAssertionStatues0、System.arrayCopy()、Float.floatToRawIntBits()、Double.doubleToRawLongBits()  \n1. 执行GetClassTest程序，得到基本数据类型的类getName()结果。\n![](images/ch09/执行GetClassTest程序.png)\n2. 执行StringTest程序，得到字符串判断的结果\n![](images/ch09/执行StringTest程序.png)\n3. 执行ObjectTest程序，得到对象的hashCode值，生成hashCode的代码是直接利用内置函数hash()生成的\n![](images/ch09/执行ObjectTest程序.png)\n4. 执行CloneTest程序，可以观察到克隆的对象与原始对象的pi值不一样\n![](images/ch09/执行CloneTest程序.png)\n5. 执行BoxTest程序，可以打印出数组的元素\n![](images/ch09/执行BoxTest程序.png)\n\n**本章总结：**  \n1. 由于invokenative指令是动态执行本地方法，又因为本地方法在不同的模块里，因此自己实现了动态加载模块，并执行对应的函数方法。\n2. 在doubleToRawLongBits本地方法中处理大数值超长的bits转换采用了如下代码：  \n   ```python\n   s = struct.pack('\u003eq', ctypes.c_uint64(bits).value)  \n   value = struct.unpack('\u003ed', s)[0]\n   ```\n3. 在产生运行时常量池时，ConstantDoubleInfo类的read_info不能直接使用ctypes进行转换，会导致float转换异常，需要用struct进行数值转换。\n   ```python\n   bytes_data = int.from_bytes(class_reader.read_unit64(), byteorder='big')  \n   self.val = struct.unpack('\u003ed', struct.pack('\u003eq', bytes_data))[0]\n   ```\n4. 重构了OperandStack、LocalVars和Slots类下面的有关double和float的get/set方法，为了检查错误，打印出了operand_stack.slots和local_vars的数据。\n\n### 第10章-异常处理\n实现了异常抛出和处理、异常处理表、athrow指令。在Java语言中，异常可以分为两类：Checked异常和Unchecked异常。Unchecked异常包括java.lang.RuntimeException、java.lang.Error以及它们的子类，其他异常都是Checked异常。所有异常都最终继承自java.lang.Throwable。如果一个方法有可能导致Checked异常抛出，则该方法要么需要捕获该异常并妥善处理，要么必须把该异常列在自己的throws子句中，否则无法通过编译。Unchecked异常没有这个限制。\n1. 执行ParseIntTest程序，输出参数123\n![](images/ch10/执行ParseIntTest-输入参数123.png)\n2. 执行ParseIntTest程序，输出参数abc\n![](images/ch10/执行ParseIntTest-输入参数abc.png)\n3. 执行ParseIntTest程序，无输出参数，会抛出异常信息\n![](images/ch10/执行ParseIntTest-无参数.png)\n\n**本章总结**  \n1. 字符串在class文件中是以MUTF-8（Modified UTF-8）方式编码的。Java序列化机制也使用了MUTF-8编码。java.io.DataInput和java.io.DataOutput接口分别定义了readUTF()和writeUTF()方法，可以读写MUTF-8编码的字符串。由于遇到了0xC0的问题，于是重构了decode_mutf8()方法，按照书中代码，也同样实现了一套字符转换，但是在python并不能将超过255的字符转换成字符串，于是查了Java1.8的代码，发现最后是强转成char，于是修改为如下代码：  \n   ```python\n   \"\".join([chr(d) for d in char_arr])\n   ```\n    ![jdk的String实现](images/ch10/jdk的String实现.png)\n2. 打印异常信息，不用像go语言那样采用反射，Python语言可以直接用如下代码执行：\n   ```ptyhon \n   for ste in ex.extra:  \n       print(\"\\tat\", ste)\n   ```\n   \n## 总结\n\u0026emsp;\u0026emsp;历时8天完成1-10章的代码，基本实现了一个JVM的功能，能提供如下命令：\n\u003e -v, --version : 版本号  \n--verbose:class : 打印类加载信息    \n--verbose:inst : 打印指令  \n--cp, --classpath : 用户类路径  \n--Xjre : jre的路径  \n\n\u0026emsp;\u0026emsp;其中遇到的问题都写在前面了，目前完成的功能有基本的命令行、class文件搜索和解析、运行时数据区、指令集和解释器、类和对象、方法调用和返回（支持迭代和递归）、数组和字符串类的加载、调用本地方法、打印异常信息。  \n\u0026emsp;\u0026emsp;还需要完成第11章的内容，未来有空会根据《JVM G1源码分析和调优》撰写一个G1的垃圾回收器。  ","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frelph1119%2Fjvmbypython","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frelph1119%2Fjvmbypython","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frelph1119%2Fjvmbypython/lists"}