Java的虚拟机内存模型

  • 时间:
  • 来源:互联网

JVM是JavaVirtualMachine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。

 

堆:

堆在虚拟机启动时就被创建,是用来存放对象的内存空间,几乎所有的对象都被放在堆中,这是一块线程共享的区域。

在规范中有这样一段描述:

所有的对象实例以及数组都要在堆上分配,但是随着JIT编译器的发展和逃逸分析技术逐渐成熟,栈上分配/标量替
换优化技术将会导致一些微妙的发生,所有的对象都分配在堆上也渐渐变得不是那么绝对了.

java堆是垃圾回收机制主要场所,所以堆还可以细分为新生代,老年代,在细分的话就是新生代分为Eden空间/From Survivor空间/To Survivor空间等。

根据java虚拟机规范,Java堆可以处于物理上不连接的但是逻辑上连续的内存空间,在实现是既可以是规定大小也可以是可扩展的,当前主流的虚拟机都是按照空间可扩展的来实现的(通过-Xmx和-Xms来控制)。如果在堆内存中没有内存完成实例分配,并且堆也无法扩展时,就会抛出内存溢出异常。

 

虚拟机栈:

虚拟机栈描述的就是java方法执行的内存模型,线程私有,栈的深度不能大于虚拟机所允许的深度,会抛出StackOverflowError异常。如果虚拟机栈是动态扩展的,在扩展时会申请内存,如果无法申请到足够的内存,会报OutOfMemoryError异常。

 栈帧  :

用于支持虚拟机进行方法调用和方法执行的数据结构。他是虚拟机运行时数据区中虚拟机栈中的栈元素。栈帧保存了 局部变量表、操作数栈、动态链接、方法返回地址等信息。

每一个方法从调用开始到结束的操作叫做方法入栈和出栈的过程。

代码在编译期间,就确定了栈帧中需要多大的局部变量表和多深的操作数栈,因此一个栈帧需要分配多少内存不会受到运行时变量数据影响,而仅仅取决于具体的虚拟机的实现。

局部变量表:是用于存储方法参数和方法内定义的局部变量的存储空间,可以存储编译期间可知的各种基本数据类型,对象引用类型和返回地址(指向一条字节码指令的地址)。表的最小单位为32位,称做变量槽,如果存储大于32位则会分配两个连续的槽空间。

操作数栈:也被称为造作栈,是一个后入先出栈。jvm底层字节码指令集是基于栈类型,所有的操作码都是对操作数栈上的数据进行操作,对于每一个方法的调用,jvm都会建立一个操作数栈,以供计算使用。操作数栈的大小也是编译的时候就已知的。操作数栈操作的是Java任意数据类型。

当一个方法刚刚开始执行时,其操作数栈是空的,随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。一个完整的方法执行期间往往包含多个这样出栈/入栈的过程。

动态链接: 在类加载阶段中解析阶段会将符号引用转为直接引用,被称为静态解析,另外一部分将在每一次运行时期转化啊为直接引用,被称为动态链接。 每个栈帧都包含了一个指向运行时常量池中该栈帧所属的方法引用,这是为了支持方法调用过程中的动态链接。

方法区:

方法区主要存储类的元数据的,如虚拟机加载的类信息、编译后的代码等。jdk 1.8之前方法区实现是被称为一种永久代的区域,这部分区域使用JVM内存,但是jdk8之后便移除了永久代,使用元空间实现,不同之处在于元空间使用的不是jvm内存,而是使用的系统内存。

在移除永久代之后,字符串常量池被放在了堆中,可能是方便垃圾回收器收集,而运行时常量池被放到了元空间里,运行时常量池内放着静态变量,字节码等重要信息,它在哪我们才能把他称为方法区。

本地方法栈:

本地方法栈和虚拟机栈类似,不同点就是 本地方法栈服务的对象是jvm执行的native方法,而虚拟机服务的是jvm 执行的java方法。至于啥事native方法,简单的解释就是java调用的非java方法。

程序计数器:

这是一块很小的内存空间,是唯一一个java虚拟机规范中没有规定内存溢出异常的区域,作用是标记当前所执行的线程所执行的行号,执行native方法时,程序计数器值为空。在虚拟机里工作原理就是通过改变这个计数器的值来确定要执行下一条的指令。我们知道JVM的多线程实现方式是通过CPU时间片轮转(即线程轮流切换并分配处理器执行时间)算法来实现的。也就是说,某个线程在执行过程中可能会因为时间片耗尽而被挂起,而另一个线程获取到时间片开始执行。当被挂起的线程重新获取到时间片的时候,它要想从被挂起的地方继续执行,就必须知道它上次执行到哪个位置,在JVM中,通过程序计数器来记录某个线程的字节码执行位置。因此,程序计数器是具备线程隔离的特性,也就是说,每个线程工作时都有属于自己的独立计数器。

 

 

 

本文链接http://element-ui.cn/news/show-2222.html