JVM主要结构
JVM结构主要由4部分组成:
- 类加载器
- 执行引擎
- 内存区
- 本地方法调用
类加载器用于在JVM启动时或者在类运行时将需要的class加载到JVM中,每个被JVM装载的类型都有一个对应的java.lang.Class类的实例来表示该类型,该实例可以唯一表示被JVM装载的clas类,要求这个实例和其他类的实例一样都存放在java堆中
执行引擎的作用就是解析JVM字节码指令,得到执行结果。在《Java虚拟机规范》中详细定义了执行引擎遇到每条字节码指令时应该处理什么,并且应该得到什么结果。但是并没有规定执行引擎应该采用何种方式处理而得到这个结果。Java的一个县城第一营了一个执行引擎实例,那么在一个JVM实例中就会同时又多个执行引擎在工作,这些执行引擎实例有的在执行用户程序,有的在执行JVM内部的程序
JVM运行时内存管理
JVM将内存划分成若干个区以模拟实际机器上的存储、记录和调度功能模块如实际机器上的各种功能的寄存器或者PC指针的记录器,执行引擎在执行一段程序时需要存储一些东西,如操作码需要的操作数,操作码执行结果需要保存。class类的字节码还有类的对象等信息都需要在执行引擎执行之前就准备好的,一个JVM实例会有一个方法去、Java堆、Java栈、PC寄存器和本地方法区。其中方法区和Java堆是所有现成共享的,也就是可以被所有的执行引擎实例访问。每个新的执行引擎实例创建时会为这个执行引擎创建一个Java栈和一个PC寄存器,如果当前正在执行一个Java方法,那么当前的这个Java栈中保存的是该线程中方法的调用状态,包括方法的参数、方法的局部变量、方法的返回值以及运算的中间结果,而PC寄存器会指向即将执行的下条指令
在Java虚拟机规范中将Java运行时数据划分为6种:
- PC寄存器数据
- Java栈
- 堆
- 方法区
- 本地方法区
- 运行时常量池
PC寄存器
PC寄存器严格来说是一个数据结构,它用于保存当前正常执行的程序的内存地址,同时Java程序时多线程执行的,所以不可能一直都按照线性执行下去,当有多个线程交叉执行时,被中断线程的程序当前执行到哪条内存地址必然要保存下来,以便于该线程恢复执行时再按照被中断时的指令地址继续执行下去,但是JVM规范之定义了Java方法需要记录指针信息,而对于Native方法,并没有要求记录执行的指针地址
Java栈
Java栈总是和线程关联在一起,每当创建一个线程时,JVM就会为这个线程创建一个对应的Java栈,在这个Java栈中又会包含有多个站帧,每个栈帧都会包含一些内部变量(在方法内部定义的变量)、操作栈和方法返回值等信息。
每当一个方法执行完成时,这个栈帧就会弹出栈帧的元素作为这个方法的返回值,并清除这个栈帧,Java栈的站定的栈帧就是当前正在执行的活动栈,也就是当前正在执行的方法,PC寄存器会指向这个地址。只有这个活动的栈帧的本地变量可以被操作栈使用,挡在这个栈帧中调用另外一个方法时,与之对应的一个新的栈帧又被创建,这个新创建的栈帧又被放到Java栈的顶部,变为当前的活动栈帧。同样现在只有这个栈帧的本地变量才能被使用,当在这个栈帧中所有指令执行完成时,这个栈帧移出Java栈,刚才操作的那个栈帧又变为活动栈帧,前面的栈帧的返回值又变为这个栈帧的操作栈的一个操作数,如果前面的栈帧操作数没有返回值,那么当前的栈帧的操作数没有变化
堆
堆是存储Java对象的地方,它是JVM管理Java对象的核心存储区域。每一个存储在堆中的Java对象会是这个对象的类的一个副本,它会复制包括继承自它父类的所有非静态属性,堆是被所有Java线程共享的,所以对它的访问需要注意同步问题,方法和对应属性都需要保证一致性
方法区
JVM方法区是用于存储类结构信息的地方,一个class文件解析成JVM能识别的几个部分,这些不同的部分在这个class被加载到JVM时会被存储在不同的数据结构中,其中常量池、域、方法数据、方法体、构造函数,包括类中专用的方法、实例初始化、接口初始化都存储在这个区域。
方法区这个存储区域也属于后面介绍的Java堆中的一部分,也就是我们通常所说的Java堆中的永久区。这个区域可以被所有的线程共享,并且它的大小可以通过参数来设置。方法区的大小一般在程序启动后的一段时间内就是固定的。
运行时常量池
运行时常量池代表每运行时每个class文件中的常量表。它包括集中常量:编译期的数字常量、方法或者域的引用。运行时常量池就是上文方法区中的常量池,它是方法区的一部分,所以它的存储也受方法区的规范约束。
本地方法栈
&emap;&emap;本地方法栈是为JVM运行Native方法准备的空间,它和前面介绍的Java栈的作用是类似的,由于很多Native方法都是用C语言实现的,所以它通常又叫C栈
JVM内存分配策略
在分析JVM内存分配策略之前我们先介绍一下通常情况下操作系统都是采用哪些策略来分配内存的
操作系统内存分配策略
在操作系统中将内存分配策略分为三种,分别是:
- 静态内存分配
- 栈内存分配
- 堆内存分配
静态内存分配是指在程序编译时就能确定每个数据在运行时的存储空间需求,因此在编译时就可以给他们分配固定的内存空间,这种分诶策略不允许在程序代码中有可变数据结构的存在,也不允许有嵌套或者递归的结构出现,因为他们都会导致编译程序无法计算准确的存储空间需求
emsp;栈式内存分配也可称为动态存储分配,是由一个类似于堆栈的运行栈类实现的。和静态内存分配相反,在栈式内存方案中,程序对数据区的需求在编译时是完全未知的,只有到运行时才知道,但是规定运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存