JVM内存区域划分

转载请标明地址:http://www.cnblogs.com/boothsun/p/8110204.html

前言

在了解JVM内存区域划分之前,先看下java程序的执行过程:

如上图所示,首先java源代码文件(.java)会被java编译器编译为java字节码文件(.class),然后由JVM类加载器加载各个类的字节码文件,加载完毕后交由JVM执行引擎执行。

在程序执行过程中,JVM会用一段空间存储运行过程中需要用到的数据和信息,这段空间称为Runtime Data Area(运行时数据区),也就是JVM内存。

我们常说的JVM内存管理,就是指对这段空间的管理(如何分配和回收内存空间)。

运行时数据区

根据《Java虚拟机规范》的规定,运行时数据区通常包括这几个部分:程序计数器(Program Counter Register)、Java栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)。

1、程序计数器

  1. 程序计数器 = 当前线程所执行的字节码行号指示器

  2. 线程私有的,生命周期与线程相同。(在JVM中,多线程是由线程轮流切换来获取CPU的执行时间片,在某一个确定的时间,一个CPU只会处理一个线程中的指令,因此,为了线程切换后能回到正确的执行位置,每条线程都需要一个独立的线程计数器,各条线程计数器互不影响,独立存储)

  3. 线程执行 Java方法时,记录其正在执行的虚拟机字节码指令地址

  4. 线程执行 Native方法时,计数器记录为Undefined)。(原因是:程序计数器记的是字节码的指令地址,而对于本地方法,是操作系统层面的方法,JVM无法记录执行过程)。

  5. 所占空间小且固定,唯一在 Java虚拟机规范中没有规定任何 OutOfMemoryError情况区域。

2、java虚拟机栈

Java栈是Java方法执行过程的内存模型:

每一个方法从调用直到执行完成的过程,就对应着一个栈帧在虚拟机栈中的入栈出栈的全过程。(线程当前执行的方法所对应的栈帧必定位于Java栈的顶部

线程执行期间,每个方法执行时都会创建一个栈帧(Stack Frame) ,用于存储 局部变量表操作数栈指向运行时常量池的引用方法返回地址等信息。

1、局部变量表

用于存储方法参数局部变量;

局部变量表在编译期间分配内存空间,可以存放编译期的各种变量类型:

  1. 基本数据类型 : booleanbytecharshortintfloatlongdouble等 8种;

  2. 对象引用类型 : reference,指向对象起始地址引用指针

  3. 返回地址类型 : returnAddress,返回地址的类型。

2、操作数栈

局部变量表不同的是,它不是通过索引来访问,而是通过标准的栈操作 — 压栈出栈来访问。

栈最典型的一个应用就是用来对表达式求值。

想想一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作栈来完成的。

3、指向运行时常量池的引用

因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。

 4、方法返回地址

当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。

当一个方法返回时,可能依次进行以下 3个操作:

  1. 恢复上层方法局部变量表操作数栈

  2. 返回值压入调用者栈帧操作数栈

  3. 将 PC计数器的值指向下一条方法指令位置。

小结:

注意:在Java虚拟机规范中,对这个区域规定了两种异常。

其一:如果当前线程请求的栈深度大于虚拟机栈所允许的深度,将会抛出 StackOverflowError 异常(在虚拟机栈不允许动态扩展的情况下);

其二:如果扩展时无法申请到足够的内存空间,就会抛出 OutOfMemoryError 异常。

3、本地方法栈

本地方法栈Java虚拟机栈发挥的作用非常相似,主要区别是 Java虚拟机栈执行的是 Java方法服务,而本地方法栈执行 Native方法服务。

4、堆

 堆是被所有线程共享的堆是用来存储对象本身的以及数组

Java中,堆被划分成两个不同的区域:新生代 ( YoungGeneration) 、老年代 ( OldGeneration) 。

新生代 ( Young) 又被划分为三个区域:一个 Eden区和两个 Survivor区 - FromSurvivor区和 ToSurvivor区。

简要归纳:新的对象分配是首先放在年轻代 ( YoungGeneration) 的 Eden区, Survivor区作为 Eden区和 Old区的缓冲,在 Survivor区的对象经历若干次收集(15)仍然存活的,就会被转移到老年代 Old中。

5、方法区

方法区和 Java堆一样,为多个线程共享,它用于存储类信息常量静态常量即时编译后的代码等数据。

运行时常量池是方法区的一部分, Class文件中除了有类的版本字段方法接口等描述信息外, 还有一类信息是常量池,用于存储编译期间生成的各种字面量符号引用

在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。

为什么将永久代转为元数据?

  1、字符串存在永久代中,容易出现性能问题和内存溢出。

  2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。

  3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

其他:

直接内存不属于虚拟机运行时数据区的一部分,也不是 Java虚拟机规范中定义的内存区域。

JavaNIO允许 Java程序直接访问直接内存,通常直接内存的速度会优于Java堆内存。因此,对于读写频繁、性能要求高的场景,可以考虑使用直接内存。

posted @ 2018-07-19 15:37  提拉米苏007  阅读(162)  评论(0编辑  收藏  举报