JVM内存结构(运行时数据区)

前言


       Java程序的运行是经过Java虚拟机来实现的。经过类加载器将class字节码文件加载进JVM,而后根据预约的规则执行。Java虚拟机在执行Java程序的过程当中会把它所管理的内存划分为若干个不一样的数据区域。这些内存区域被统一叫作运行时数据区。Java运行时数据区大体能够划分为5个部分。在这里要特别指出,咱们如今说的JVM内存划分是概念模型。以下图所示:

JVM
JVM运行时数据区分为5种:java

  • 程序计数器

  • 虚拟机栈(java栈)

  • 方法区

  • 本地方法栈

程序计数器


       程序计数器是一块较小的内存空间,它能够当作是当前线程所执行的字节码的行号指示器。程序计数器记录线程当前要执行的下一条字节码指令的地址。因为Java是多线程的,因此为了多线程之间的切换与恢复,每个线程都须要单独的程序计数器,各线程之间互不影响。这类内存区域被称为“线程私有”的内存区域。

       若是线程在执行的是一个Java方法,计数器记录的是正在执行的虚拟机字节码指令地址,若是正在执行的是Native方法,计数器值则为空。因为程序计数器只存储一个字节码指令地址,故此内存区域没有规定任何OutOfMemoryError状况。

 

虚拟机栈


     与程序计数器同样,虚拟机栈也是线程私有的,生命周期与线程相同。

     虚拟机栈描述的是Java方法执行的内存模型,每一个方法执行的时候都会同时建立一个栈帧(用于存储局部变量表、操做栈、动态连接、方法出口等信息)

     一个栈帧就表明了一个方法执行的内存模型,虚拟机栈中存储的就是当前执行的全部方法的栈帧(包括正在执行的和等待执行的)。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。咱们平时所说的“局部变量存储在栈中”就是指方法中的局部变量存储在表明该方法的栈帧的局部变量表中。而方法的执行正是从局部变量表中获取数据,放至操做数栈上,而后在操做数栈上进行运算,再将运算结果放入局部变量表中,最后将操做数栈顶的数据返回给方法的调用者的过程。

     局部变量表存放了编译期可知的各类基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同与对象自己,根据不一样的虚拟机实现,它多是一个指向对象起始地址的引用指针,也多是指向一个表明对象的句柄或者其余与此对象相关的位置)和returnAddress类型(指向一条字节码指令的地址)。

     64为长度的long和double类型的数据会占2个局部变量空间(Slot),其他的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法须要在帧中分配多大的局部变量空间是彻底确实肯定的,在方法运行期间不会改变局部变量表的大小。

      虚拟机栈可能出现两种异常:由线程请求的栈深度过大超出虚拟机所容许的深度而引发的StackOverflowError异常;以及由虚拟机栈没法提供足够的内存而引发的OutOfMemoryError异常。

 


      Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被全部线程共享的一块内存区域,在虚拟机启动时建立。此内存区域的惟一目的就是存放对象实例:全部的对象实例以及数组都要在堆上分配。但Class对象比较特殊,它虽然是对象,可是存放在方法区里。

     Java堆是垃圾收集器(GC)管理的主要区域。如今的收集器基本都采用分代收集算法:新生代和老年代。而对于不一样的”代“采用的垃圾回收算法也不同。通常新生代使用复制算法;老年代使用标记整理算法。对于不一样的”代“,通常使用不一样的垃圾收集器,新生代垃圾收集器和老年代垃圾收集器配合工做。

    Java堆能够是物理上不连续的内存空间,只要逻辑上连续便可。Java堆可能抛出OutOfMemoryError异常。

 

方法区


       方法区与Java堆同样,是各个线程共享的内存区域。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

      全部的字节码被加载以后,字节码中的信息:类信息、类中的方法信息、常量信息、类中的静态变量等都会存放在方法区。正如其名字同样:方法区中存放的就是类和方法的全部信息。此外,若是一个类被加载了,就会在方法区生成一个表明该类的Class对象(惟一一种不在堆上生成的对象实例)该对象将做为程序访问方法区中该类的信息的外部接口。有了该对象的存在,才有了反射的实现。

      在Java7以前,HotSpot虚拟机中将GC分代收集扩展到了方法区,使用永久代来实现了方法区。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。可是在以后的HotSpot虚拟机实现中,逐渐开始将方法区从永久代移除。Java7中已经将运行时常量池从永久代移除,在Java 堆(Heap)中开辟了一块区域存放运行时常量池。而在Java8中,已经完全没有了永久代,将方法区直接放在一个与堆不相连的本地内存区域,这个区域被叫作元空间。

     根据Java虚拟机规范的规定,当方法区没法知足内存分配需求时,将抛出OutOfMemoryError异常。

 

本地方法栈


       本地方法栈与虚拟机栈相似,他们的区别在于:本地方法栈用于执行本地方法(Native方法);虚拟机栈用于执行普通的Java方法。在HotSpot虚拟机中,就将本地方法栈与虚拟机栈作在了一块儿。

       本地方法栈可能抛出的异常同虚拟机栈同样。

 

运行时常量池


      运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、借口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各类字面量和符号引用,这部份内容将在类加载后存放到方法区的运行时常亮池中。

      运行时常量池相对于Class文件常量池的一个重要特征是动态性,Java语言并不要求常量必定只能在编译期产生,也就是并不是预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性用的比较多的是String类的intern()方法。

       运行时常量池在JDK1.6及以前版本的JVM中是方法区的一部分,而在HotSpot虚拟机中方法区放在了”永久代(Permanent Generation)”。因此运行时常量池也是在永久代的。

      可是JDK1.7及以后版本的JVM已经将运行时常量池从方法区中移了出来,在Java 堆(Heap)中开辟了一块区域存放运行时常量池。

 

参考

《深刻理解Java虚拟机-JVM高级特性与最佳实践》第二版 周志明著

相关文章
相关标签/搜索