JVM-内存模型

JVM-内存模型

JVM大致将内存数据分为程序计数器、虚拟机栈、本地方法栈、Java堆、方法区。

 程序计数器用于存放下一条运行指令、虚拟机栈和本地方法栈用于存放函数调用的堆栈信息、Java堆用于存放Java程序运行时产生的对象数据、方法区用于存放程序的类元数据信息。

1、程序计数器

 程序计数器是线程私有的用于记录当前线程下一条要执行的指令,若当前线程正在执行Native方法,则程序计数器为空。

2、Java虚拟机栈

Java虚拟机栈也是线程私有的,与线程同一时间创建,保存方法的局部变量、部分结果,并参与方的的调用和返回。

虚拟机栈在运行时使用一个叫做栈帧的数据结构保存上下文数据。栈帧存放了方法的局部变量表、操作数栈、动态连接方法和返回地址等信息。
 

2.1、局部变量表

局部变量表是一组变量值的空间,用于存放方法参数和局部变量。虚拟机还会将当前对象(this)作为参数通过局部变量表传递给当前方法。在Class 文件的方法表的 Code 属性的 max_locals 指定了该方法所需局部变量表的最大容量。

变量槽 (Variable Slot)是局部变量表的最小单位,一个 Slot 可以存放 boolean、byte、char、short、int、float、reference 和 returnAddress 8种类型。其中 reference 表示对一个对象实例的引用,通过它可以得到对象在Java 堆中存放的起始地址的索引和该数据所属数据类型在方法区的类型信息。returnAddress 则指向了一条字节码指令的地址。

对于64位的 long 和 double 变量而言,虚拟机会为其分配两个连续的 Slot 空间。局部变量表所需的内存空间在编译期确定,当进入一个方法时,方法在栈帧中所需要分配的局部变量控件是完全确定的,不可动态改变大小。

线程请求的栈帧深度大于虚拟机所允许的深度---StackOverFlowError,如果虚拟机栈可以动态扩展(大部分虚拟机允许动态扩展,也可以设置固定大小的虚拟机栈),但是无法申请到足够的内---OutOfMemorError。

2.2、操作数栈

操作数栈(Operand Stack)也常称为操作栈,是一个后入先出栈。在Class 文件的Code 属性的 max_stacks 指定了执行过程中最大的栈深度。

Java 虚拟机的解释执行引擎称为”基于栈的执行引擎“,这里的栈就是指操作数栈。

操作数栈可以存放一个jvm中定义的任意数据类型的值。

在任意时刻,操作数栈都一个固定的栈深度,基本类型除了long、double占用两个深度,其它占用一个深度。

2.3、动态连接

每个栈帧都包含一个执行运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。

Class 文件中存放了大量的符号引用,字节码中的方法调用指令就是以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或第一次使用时转化为直接引用(如final、static域等),这种转化称为静态解析。另一部分将在每一次运行期间转化为直接引用,这部分称为动态连接

2.4、方法返回地址

当一个方法被执行后,有两种方式退出该方法,return和异常

       当执行遇到返回指令,会将返回值传递给上层的方法调用者,这种退出的方式称为正常完成出口(Normal Method Invocation Completion)

       当执行遇到异常,并且当前方法体内没有得到处理,就会导致方法退出,此时是没有返回值的,称为异常完成出口(Abrupt Method Invocation Completion)

无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。

一般来说,方法正常退出时,调用者的PC计数器的值就可以作为返回地址,栈帧中很可能保存了这个计数器值,而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。

方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,如果有返回值,则把它压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令。

2.5、附加信息

虚拟机规范并没有规定具体虚拟机实现包含什么附加信息,这部分的内容完全取决于具体实现。在实际开发中,一般会把动态连接,方法返回地址和附加信息全部归为一类,称为栈帧信息

3、本地方法栈

本地方法栈和Java虚拟机栈类似,Java虚拟机栈管理java函数的调用,本地方法栈管理本地方法的调用。HotSpot虚拟机不区分Java虚拟机栈和本地方法栈。

4、Java堆

Java堆是所有线程共享的内存区,在虚拟机启动时创建。这个区域是用来存放对象实例的,几乎所有对象实例都会在这里分配内存,同时也是GC的主要区域。

Java堆可以细分为:新生代和老年代;再细致一点的有Eden空间,From Survivor(s0)空间,To Survivor(s1)空间和tenured区(老年代)等。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。可以通过-Xmx和-Xms控制。s0和s1是两块大小相等可以互换的内存空间。

在大多数情况下,对象首先被分配在eden区,再一次新生代回收后,如果对象还存在,则会进入s0或者s1,之后没经过一个新生代回收,年龄+1,当对象年龄达到一定条件后,就会老龄化进入老年代。

5、方法区

方法区也叫永久代,也是所有线程共享的内存区,用于存储已经被虚拟机加载过的类信息、常量、静态变量(JDK7中被移到Java堆),即时编译期编译后的代码(类方法)等数据。

运行时常量池,它是方法区的一部分,用于存放编译期生成的各种字面量和符号引用(其实就是八大基本类型的包装类型和String类型数据(JDK7中被移到Java堆))。

在JDK1.7中的HotASpot中,已经把原本放在方法区的字符串常量池移出。

  • 将interned String移到Java堆中
  • 将符号Symbols移到native memory(不受GC管理的内存)

从JDK7开始永久代的移除工作,贮存在永久代的一部分数据已经转移到了Java Heap或者是Native Heap。但永久代仍然存在于JDK7,并没有完全的移除:符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。

在JDK8中,JVM不再有PermGen。但类的元数据信息(metadata)还在,只不过不再是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory)中。

参考:

http://blog.csdn.net/zq602316498/article/details/38926607

http://www.cnblogs.com/SaraMoring/p/5713732.html

http://www.cnblogs.com/niejunlei/p/5987611.html

《Java程序性能优化  让你的Java程序更快、更稳定》

猜你喜欢

转载自214324070.iteye.com/blog/2380021