# 简单的jvm编译器 **Repository Path**: ni-zewen/simple-jvm-compiler ## Basic Information - **Project Name**: 简单的jvm编译器 - **Description**: 用GO实现简单的jvm编译器 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 1 - **Created**: 2021-09-07 - **Last Updated**: 2023-01-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: Go语言 ## README > 此文件为 jvm 编译器和及其知识的相关说明 > 包括 > - 知识补充 > - 运行时的数据区域 > - 编译器解析 > - 命令行工具 > - 搜索 class > - 解析 class 文件 > - 运行时数据区 > - 指令集和解释器 > - 类与对象 > 引用文章 > https://blog.csdn.net/ylyuanlu/article/details/18947951 栈帧详解 > https://github.com/Snailclimb/JavaGuide jvm 部分 > https://www.cnblogs.com/pingxin/p/p00081.html 用 go 实现 jvm > 《深入浅出 Java 虚拟机》 > 《Java 高并发之美》 ## 基础知识 - **栈帧** 表示程序的函数调用记录,而栈帧又是记录在栈上面,很明显栈上保持了 N 个栈帧的实体,(实际上我们这里说的栈帧是软件上的概念,据说有硬件概念,不是很了解),那就可以说栈帧将栈分割成了 N 个记录块,但是这些记录块大小不是固定的,因为栈帧不仅保存诸如:函数入参、出参、返回地址和上一个栈帧的栈底指针等信息,还保存了函数内部的自动变量(甚至可以是动态分配内存,alloca 函数就可以实现,但在某些系统中不行),因此,不是所有的栈帧的大小都相同。 - **jVM 是虚拟机**,总的来说是一种标准规范,虚拟机有很多实现版本。主要作用就是运行 java 的类文件的。 **HotSpot 是虚拟机的一种实现**,它是 sun 公司开发的,是 sun jdk 和 open jdk 中自带的虚拟机,同时也是目前使用范围最广的虚拟机。 - **句柄**,用一种形象的说法可以表述为:有一个固定的地址(句柄),指向一个固定的位置(区域 A),而区域 A 中的值可以动态地变化,它时刻记录着当前时刻对象在内存中的地址。这样,无论对象的位置在内存中如何变化,只要我们掌握了句柄的值,就可以找到区域 A,进而找到该对象。而句柄的值在程序本次运行期间是绝对不变的,我们(即系统)当然可以掌握它。这就是以不变应万变,按图索骥,顺藤摸瓜。(相当于**指针的指针**) ## Ⅰ 知识补充 ### 1. 概述 虚拟机自动内存管理的机制下,不再需要像 C/C++程序开发那样为 new 操作写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出。所以一旦出现也难于排查,需要了解虚拟机的怎么样使用内存。 ### 2. 运行时的数据区域  JDK1.7 中已经将运行时常量池从方法区中移除,在 Java 堆中开辟一块区域存放常量池。 #### 2.1 程序计数器 (指示器) 程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的**行号指示器**。**字节码解释器工作时通过改变这个计数器的值来选取一条需要执行的字节码指令,分支循环异常处理线程恢复等功能都需要依赖计数器完成** 所以为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各个线程之间计数器互不影响,称这类内存区域为“**线程私有**”的内存 其主要有俩个作用 - 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制 - 在多线程的情况下,程序计数器用来记录当前线程执行的位置,用来恢复现场 #### 2.2 Java 虚拟机栈 (索引) Java 虚拟机栈也是现场私有的,其生命周期和线程相同,**描述 Java 方法执行的内存模型**。 Java 内存可以粗糙的分为堆内存和栈内存,其中栈就是现在说的虚拟机栈,即**虚拟机中局部变量表的部分**(实际上,Java 虚拟机栈是由一个个栈帧组成,每个栈帧中都有局部变量表,操作数栈,动态链接,方法出口信息。) **局部变量表主要存放编译器可知的各种数据类型,对象引用**(reference 类型,可能是一个指向对象起始地址的**引用指针**,也可能是指向**一个代表对象的句柄或其他与此对象相关的位置**)
Java 虚拟机栈会出现俩种异常:StackOverFlowError 和 OutOfMemoryError
- **StackOverFlowError** Java 虚拟机栈的内存不允许动态拓展,当请求栈的深度超过 - 当前 Java 虚拟机栈的最大深度,就抛出 StackOverFlowError 异常 **OutOfMemoryError** Java 虚拟机栈的内存允许动态拓展,且当线程请求时栈内存用完 **Java 虚拟机栈也是线程私有的,每个线程有各自的虚拟机栈**,且生命周期和线程相同 #### 2.3 本地方法栈 **虚拟机栈为虚拟机执行的 Java 方法(字节码)服务,而本地方法栈使用到的 native 方法服务** 在 HotSpot 和 Java 虚拟机中合二为一。 其调用方式,储存方式,异常抛出方式和虚拟机栈类似。 #### 2.4 堆 (内存) Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。**此区域的唯一目的是存放对象实例,几乎所有的对象实例和数组都在这里分配内存。** **Java 堆是垃圾收集器管理的主要区域,所以也被称作 GC 堆(Garbage Collected Heap)** 从垃圾回收的角度 Java 堆可分为 Eden 空间等。**进一步划分的目的是为了更好的回收内存,或更快的分配内存** 在 JDK1.8 中移除整个永久代,取而代之的一个元空间(前者使用 JVM 堆,后者使用的是物理内存) #### 2.5 方法区 (常量和类信息) **方法区和 Java 堆一样,是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据**虽然是堆的逻辑部分,但其别名是非堆。 **相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就永久存在了** #### 2.6 运行时的常量池 运行时常量池是方法区的一部分。Class 文件中除了类的版本,字段,方法等描述信息外,还有常量池信息当常量池无法再申请到内存时会抛出OutOfMemoryRerror异常
#### 2.7 直接内存 其不是虚拟机运行时的一部分,但是也被频繁的使用,且也有可能导致OutOfMemoryError的出现
JDK1.4 中新加入的 NIO 类引入基于通道的缓冲区 I/O 方式,可以直接使用 Native 函数库直接分配堆外内存,然后通过 Java 堆中的对象作为此内存的引用直接操作,避免了 Java 堆和 Native 堆之间来回复制数据。(**此时堆中的对象作为引用,之间内存中的区域作为储存空间**) ### 3. HotSpot 虚拟机对象解密 HotSpot 虚拟机在 Java 堆中对象分配,布局和访问的全过程下列内容需要熟练掌握
#### 3.1 对象的创建  1. **类加载检查** 虚拟机遇到一条 **new 指令**时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过,解析和初始化过。如果没有,则需要先执行相应的类的加载过程。 2. **分配内存** 在类加载检查通过后,接下来虚拟机将为新生对象分配内存,对象所需要的内存大小在类加载完成后便可以确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分除了。**分配方式有指针碰撞和空闲列表俩种,选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整由所采用的垃圾收集器是否带有压缩整理功能决定。** 下列内容尚未理解
> **内存分配的并发问题** > 在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发中,创建对象是很频繁的事情。作为虚拟机来说,必须要保证线程是安全的,虚拟机采用俩种方式来保证线程的安全。 > > - CAS+失败重试 CAS 是乐观锁的一种实现方式,**虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。** > - TLAB 3. **初始化零值** 内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值,这一步保证了对象的实例字段在 Java 代码中可以不赋初值就直接使用,程序能访问到这些字段对应的零值。 4. **设置对象头** 初始化零值后,**虚拟机要对对象进行必要的设置**,例如这个对象室那个类的实例,如何才能找到类的元数据信息,对象的哈希码等,这些信息存在对象头中。 5. **执行 init 方法** 在上面的工作都完成后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的角度来看,对象创建才刚开始。一般来说,**执行 new 指令之后会接着执行\