【JVM】内存结构模型

注:本篇主要讲解jvm的内存结构、各部分的作用以及各部分之间的潜在联系。
在这里插入图片描述

一、类加载子系统

java虚拟机的启动,首先是通过类加载子系统中的引导类加载器创建一个初始类来完成的。这个初始类是虚拟机具体实现需要的class信息,加载后的信息存放在一块称之为方法区的内存空间,至于是否可以运行,需要看执行引擎的工作。

类加载过程

类加载过程中主要包含三个步骤:加载——链接——初始化,而链接过程中又分为:验证——准备——解析
在这里插入图片描述

1、加载

  • 通过一个要加载类的权限定名获取定义此类的二进制字节流。
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为这个类的各种数据在方法区的访问入口。

常见类加载器

jvm支持两种类型的类加载器,引导类加载器(Bootstrap ClassLoader )和自定义类加载器(User-Defined ClassLoader),在java虚拟机规范中,将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。

引导类加载器-Bootstrap ClassLoader
引导类加载器,又叫启动类加载器,是虚拟机自带的一个加载器,用C/C++语言编写,用来加载jvm核心库,提供jvm自身需要的类。此类并不继承java.lang.ClassLoader,没有父加载器。是扩展类和应用程序类加载器的父类。
出于安全考虑,启动类加载器只加载包名为java、javax、sun等开头的类。

扩展类加载器-Extension ClassLoader
扩展类加载器,有java语言编写,并且派生于ClassLoader类,父类加载器为启动类加载器。
此加载器从java.ext.dirs系统属性所指定的目录中加载类库,或从jdk的安装目录jre/lib/ext子目录下加载类库、如果用户创建的jar放在此目录下,也会自动由扩展类加载器加载。

系统类加载器-AppClassLoader
系统类加载器又叫应用程序类加载器,派生于ClassLoader类,父类加载器为扩展类加载器。
负责加载环境变量classpath或系统属性java.class.path指定路径下的类库。该类加载器是程序中默认的类加载器,一般来说,java应用的类都是由它来完成的。

双亲委派机制

java虚拟机对class文件采用的是按需加载的方式,当需要使用该类是才会将它的class文件加载到内存生成class对象。而在加载某个类的class文件时,java虚拟机采用的是双亲委派模式。

原理

  • 如果一个类加载器收到了类加载请求,他并不会自己先去加载,而是把这个请求委托给父类的加载器去执行。
  • 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
  • 如果父类就下载器可以完成类加载任务,就成功返回,无法完成,子加载器才会尝试自己去加载
    在这里插入图片描述

2、链接

在这里插入图片描述

3、初始化

在这里插入图片描述

二、运行时数据区

在这里插入图片描述

1、方法区

方法区又被称为永久区,被所有线程共享。用于存放类的信息、常量信息、常量池信息,包括字符串字面量和数字常量等。类信息被加载类子系统加载后,会被存放在方法区进行,而类实例则被存放在堆中。

运行时常量池:是方法区中的一部分,class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用与存放编译期生成的各种字面量和符号引用,这部分内容将在加载进入方法区的运行时常量池中存放。当方法区中内存满后,会触发垃圾回收机制,对方法区中的数据进行垃圾回收。

2、java堆

在java虚拟机启动时建立,是java程序最主要的内存工作区域,用于存放有new创建的对象和数组,几乎所有的对象实例都存放到java堆中,被所有线程共享。堆中的内存空间也由java虚拟机自动垃圾回收器来管理。

在堆中产生一个数组或对象后,还会在栈中定义一个特殊的变量,这个变量的取值是堆中这个数组或对象在堆内存中的首地址,栈中的这个变量就是这个数组或对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象。

根据垃圾回收机制的不同,Java堆有可能拥有不同的结构,最为常见的就是将整个Java堆分为新生代和老年代。其中新声带存放新生的对象或者年龄不大的对象,老年代则存放老年对象。新生代分为den区、s0区、s1区,s0和s1也被称为from和to区域,他们是两块大小相等并且可以互相角色的空间。

绝大多数情况下,对象首先分配在eden区,在新生代回收后,如果对象还存活,则进入s0或s1区,之后每经过一次新生代回收,如果对象存活则它的年龄就加1,对象达到一定的年龄后,则进入老年代。
在这里插入图片描述

3、虚拟机栈(运行时栈)

堆是存储的单位,而栈是运行时的单位。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧,对应着一次次的java方法调用。jvm对java栈的操作有两个:方法执行进栈、执行结束出栈。所以栈不存在垃圾回收机制。

虚拟机栈主要的作用是:主管java程序的运行,它保存方法的局部变量、部分结果,并参与方法的调用和返回。

原理
不同线程中所包含的栈帧是不允许存在相互引用的,即不可能在一个栈帧中引用另一个线程的栈帧。
如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着,虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧。

栈帧结构

1、局部变量表

  • 定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量。
  • 容量大小在编译期确定,在方法运行期间不会改变。但其大小会影响栈中方法嵌套的次数,容量越大,嵌套次数越少。
  • 局部变量表中的变量只在当前方法调用中有效,随着方法栈帧的销毁而销毁。
  • 最基本存储单元slot(变量槽)

变量槽-slot

  • 当实例方法被调用时,方法参数和方法体内部定义的局部变量将会按照顺序被复制到局部变量表中的每一个slot上。
  • 32位以内的类型占用一个slot(包括returnAddress类型),64位的类型(long和double)占用两个slot。
  • byte、short、char在存储前被转换为int,boolean也被转换为int,0表示false,非0表示true。
  • jvm会为每一个slot都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量值。
  • 如果需要访问一个64位的局部变量值时,只需要使用该变量的第一个slot的索引即可(64位的占用两个slot)。
  • 如果当前帧是由构造方法或者实例方法创建的,那么该对象引用this将会存放在index为0的slot上,其余参数按照参数表顺序继续排列。
  • slot是可以重复利用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变量就很有可能会复用过期的局部变量槽位,达到节省资源的目的。

2、操作数栈

又称表达式栈——后进先出。在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据(入栈、出栈)。主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。但只能通过标准的入栈出栈操作来完成一次数据访问。

操作数栈可以是jvm执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧随之被创建,此时这个方法的操作数栈是空的。

操作数栈拥有一定栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在方法的code属性中,为max_stack的值。32bit的类型占用一个栈单位深度,64bit的类型占用两个栈单位深度。如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中,并更新pc寄存器中下一条需要执行的字节码指令。

3、方法返回地址

方法返回地址存放调用该方法的pc寄存器的值,当方法退出后都会返回到该方法被调用的位置(正常退出:调用者的pc计数器的值作为返回地址。异常退出:返回地址要通过异常表来确定,栈帧中一般不会保存这部分信息)。

本质:方法的退出就是当前栈帧出栈的过程。至此,需要恢复上层方法的局部变量表、操作数栈、将返回值压入调用者栈帧的操作数栈、设置pc寄存器值等,让调用者方法继续执行下去。

4、动态链接

5、一些附加信息

4、本地方法栈

jvm虚拟机栈用于管理java方法的调用,而本地方法栈用于管理本地方法的调用。本地方法栈用c语言实现,是线程私有的。本地方法中通过链接到本地方法接口,来调用本地方法库中的方法。当某个线程调用一个本地方法时,他就进入了一个全新的并且不再受虚拟机限制的世界,他和虚拟机拥有同样的权限

5、程序计数器

作用:用来存储指向下一条指令的地址,即即将要执行的指令代码,由执行引擎读取下一条指令。
同时也存储当前方法的jvm指令地址,如果是在执行本地方法,则是未指定(undefned)。
为了能够准确的记录各个线程正在执行的当前字节码指令地址,最好的办法是为每一个线程都分配一个pc寄存器。

三、执行引擎

虚拟机的核心组件,负责执行虚拟机的字节码,一般用户先进行编译成机器码后再执行。

四、本地方法接口

本地方法接口用于向本地方法栈中提供本地方法(使用native关键字修饰的java方法,我们称为本地方法),是java方法调用本地方法的一个桥梁,同时也起到了融合不同的编程语言为java所用。

一个Native Method就是一个java调用非java代码的 接口。定义一个native method时,并不提供实现体(有些像定义一个java interface),因为其实现体是由非java语言在外面实现的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章