JVM结构组成

JVM结构组成

JVM结构组成概览

image-20221010171811373

组成说明:

JVM包含两个子系统和两个组件:两个子系统为类加载子系统、执行引擎,两个组件为运行时数据区、本地接口。

工作流程:首先通过编译器把Java代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由CPU去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

JVM字节码执行引擎:虚拟机核心的组件就是执行引擎,它负责执行虚拟机的字节码,一般先进行编译成机器码后执行。虚拟机是一个相对于物理机的概念,虚拟机的字节码是不能直接在物理机上运行的,需要JVM字节码执行引擎编译成机器码后才可在物理机上执行。

JVM运行时数据区

运行时数据区概览

Java虚拟机在执行Java程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而建立和销毁。Java虚拟机所管理的内存被划分为如下几个区域:

image-20221010174206967

程序计数器

程序计数器(Program Counter Register):也可以把它叫做线程计数器(线程是不具备记忆功能,所以要借助计数进行线程的切换)。当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成。程序计数器是一块较小的内存空间,它可以看作是:保存当前线程所正在执行的字节码指令的地址(行号)。由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。称之为线程私有的内存。程序计数器内存区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。

虚拟机栈

image-20221011103027580

虚拟机栈说明:

1)Java虚拟机栈(Java Virtual Machine Stacks):每个方法在执行的同时都会在Java虚拟机栈中创建一个栈帧(Stack Frame,栈帧就是Java虚拟机栈中的下一个单位)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

2)Java虚拟机是线程私有的,它的生命周期和线程相同。虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。虚拟机栈中是有单位的,单位就是栈帧,一个方法一个栈帧。

栈帧存储内容说明如下:
局部变量表:是用来存储我们临时8个基本数据类型、对象引用地址、returnAddress类型。(returnAddress中保存的是return后要执行的字节码的指令地址。)
操作数栈:操作数栈就是用来操作的,例如代码中有个i=6*6,他在一开始的时候就会进行操作,读取我们的代码,进行计算后再放入局部变量表中去。
动态链接:假如我方法中,有个service.add()方法,要链接到别的方法中去,这就是动态链接,存储链接的地方。
出口:出口是什呢,出口正常的话就是return,不正常的话就是抛出异常。

3)一个方法调用另一个方法,会创建很多栈帧,如果一个栈中有动态链接调用别的方法,就会去创建新的栈帧,栈中是有顺序的,一个栈帧调用另一个栈帧,另一个栈帧就会排在调用者下面。栈指向堆是指栈中要使用成员变量时,栈中不会存储成员变量,只会存储一个应用地址。递归的话也会创建多个栈帧,就是在栈中一直从上往下排下去。

本地方法栈、堆、方法区

1)本地方法栈(Native Method Stack)
与虚拟机栈的作用是一样的,只不过虚拟机栈是服务Java方法的,而本地方法栈是为虚拟机调用Native方法服务的。Native关键字修饰的方法是看不到的,Native方法的源码大部分都是C和C++的代码。他和栈很像,只不过是方法上带了native关键字的栈。它是虚拟机栈为虚拟机执行Java方法(也就是字节码)的服务方法。native关键字的方法是看不到的,必须要去oracle官网去下载才可以看的到,而且native关键字修饰的大部分源码都是C和C++的代码。同理可得,本地方法栈中就是C和C++的代码。

2)Java堆(Java Heap)
Java虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存。堆在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。堆是垃圾收集器管理的主要区域,因此也被称为“GC堆”。从内存回收角度来看堆可分为:新生代和老生代。从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。无论怎么划分,都与存放内容无关,无论哪个区域,存储的都是对象实例,进一步的划分都是为了更好的回收内存,或者更快的分配内存。根据Java虚拟机规范的规定,堆可以处于物理上不连续的内存空间中。当前主流的虚拟机都是可扩展的(通过 -Xmx 和 -Xms 控制)。如果堆中没有内存可以完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

3)方法区(Methed Area)
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。方法区是所有线程共享的内存区域,它有个别命叫Non-Heap(非堆)。当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。

堆栈的区别

1)物理地址
堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如标记清除、复制、标记压缩、分代(即新生代使用复制算法,老年代使用标记压缩)。
栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的,所以性能快。

2)内存
堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定,一般堆大小远远大于栈。
栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。

3)存放的内容
堆存放的是对象的实例和数组,因此该区更关注的是数据的存储。
栈存放局部变量、操作数栈、返回结果,该区更关注的是程序方法的执行。

4)程序的可见度
堆对于整个应用程序都是共享、可见的。
栈只对于线程是可见的,所以也是线程私有,他的生命周期和线程相同。
posted @ 2022-12-01 16:10  肖德子裕  阅读(150)  评论(0编辑  收藏  举报