一、运行时数据区
其中方法区和堆为线程共享区域,虚拟机栈和本地方法栈及程序计数器为线程私有数据区
二、各区域功能
2.1 程序计数器
程序计数器是一块较小的内存空间,可以看作当前线程所执行字节码的行号指示器。在多线程上下文切换时用来确保切换后能恢复到之前线程的执行位置。
如果线程正在执行的是一个Java方法,该计数器记录的是正在执行的虚拟机字节码指令地址;如果正在执行的是Native方法,该计数器值则为空。该片内存区域是唯一一个在Java虚拟机规范中没有OutOfMemoryError情况的区域
2.2 虚拟机栈
2.2.1 概念
虚拟机栈用来存储局部变量表、操作数栈、动态链接、方法出口等信息。虚拟机栈与程序计数器一样也是线程私有的,它的生命周期与线程相同。
当一个新方法在线程中被调用时,就会在栈中创建一个栈帧,当方法调用完成后出栈
2.2.2 局部变量表
局部变量表是一个数组,用于存储方法参数、方法内的局部变量(编译器克制的基本数据类型及对象引用)和方法返回类型。
2.2.3 操作数栈
主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。
2.2.4 动态链接
动态链接也称为运行时常量池的方法引用。在每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。当一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
也就是说:当一个方法调用另一个方法时,不会再创建一个被调用的方法,而是通过常量池的方法引用来调用,而这个区域存储的就是运行时常量池的方法引用,这个区域的作用就是将运行时常量池的符号引用转换成直接引用
2.2.5 方法返回地址
方法返回地址存放的是调用该方法的程序计数器的值。程序计数器里面保存的是该线程要执行的下一行指令的位置。
也就是说:在一个方法中调用了另一个方法,当被调用的方法执行完之后,要执行的下一行指令就是保存在此区域的。
Java虚拟机规范中对该区域规定了两种异常状况: 1.如果线程请求栈深度大于虚拟机允许的深度将抛出StackOverFlowError异常 2.如果虚拟机栈可以动态扩展,扩展时无法申请到足够的内存空间抛出OutofMemoryError异常
2.3 本地方方法栈
本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈为虚拟机执行Native方法服务。需要注意的是,由于虚拟机规范对于本地方法栈的具体实现没有做强制要求,所以Sun HotSpot直接把本地方法栈和虚拟机栈合二为一。
2.4 Java堆
Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,该内存区域唯一目的就是存放对象实例,几乎所有对象实例都在这里分配。
Java堆是Javaer需要重点关注的一块区域,因为涉及到内存的分配(new关键字,反射等)与回收(回收算法,收集器等)。在Java虚拟机规范中的描述是:所有对象实例以及数组都要在堆上分配。需要特别注意的是,线程共享的Java堆中可能分出多个线程私有的分配缓冲区(TLAB,这是为了并发分配内存时的脏分配问题,需要使用相关参数来开启。虚拟机默认使用CAS加上失败重试机制解决脏分配问题)。此外,Java堆在HotSpot中的实现是可扩展的。参数-Xmx/-Xms来控制
Java堆是垃圾收集器管理的主要区域。从垃圾回收的角度来看,由于现在收集器基本都采用分代垃圾收集算法,因此Java堆可以细分为新生代、老年代,再细致点还可以分为Eden空间、From Survivor、To Survivor空间等。进一步细分堆内存是为了更好、更快的回收分配内存。
在JDK 7版本及之前,堆内存通常被分为下面三部分:
- 新生代内存(Young Generation)
- 老年代(Old Genertion)
- 永生代(Permanent Generation)
JDK 8版本之后方法区(HotSpot永久代)被彻底移除了,取而代之是元空间,元空间使用的是直接内存。
上图所示Eden区,两个Survivor区都属于新生代(为了区分,这两个Survivor区域按照顺序被命名为from和to),中间一层属于老年代。
大部分情况,对象都会首先在Eden区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入s0或者s1,并且对象年龄还会加一岁(Eden区-》Survivor区后对象初始年龄变为1),当它的年龄增加到一定程度,就会被晋升为老年代中。
2.5 方法区
方法区与Java堆一样为线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区有一个别名叫永久代(Permanent Generation),这是因为HotSpot设计团队把GC分代收集扩展至方法区,这样HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内存,能够省钱专门为方法区编写内存管理代码的工作。对于其他虚拟机(J9)等,是没有永久代这个概念的。
永久代的配置参数: -XX:MaxPermSize
2.6 运行时常量池
运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量("zdy","123"等)和符号引用。运行时常量池具有动态性,并非只有Class文件中的内容才能进入运行时常量池,运行期间也能将新的常量放入池中。如String.intern()方法。既然运行时常量池是方法区的一部分,自然收到方法区内存的限制,当常量池无法申请到内存时会抛出OutOfMemoryError错误。
-
JDK1.7前运行时常量池逻辑包含字符串常量池存放在方法区,此时hotspot虚拟机对方法区的实现为永久代
-
JDK1.7字符串常量池被从方法区拿到堆中,此处未提及运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区中。
-
JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之,这时候字符串常量池还在堆,运行时常量池还在方法区,只不过方法区的实现从永久代变成了元空间(Metaspace)