运行时数据区主要包括:方法区,堆,Java 虚拟机栈,程序计数器,本地方法栈。
其中方法区和堆所有线程共享,Java栈,程序计数器,本地方法栈线程私有。
程序计数器
一块较小的内存空间,可以看做是当前线程所执行的字节码行号的指示器;
字节码解释器工作时,通过改变计数器的值 选取下一条执行的字节码指令;(一些基本功能都需要依赖计数器来完成如:分支、循环、跳转、异常处理、线程恢复等)
Java 虚拟机多线程是通过线程间轮流切换来分配给处理器执行时间;在确定时间节点,一个处理器(一核)只会执行一个线程的指令。
为保证线程切换回来后能恢复到原执行位置,各个线程间计数器互相不影响,独立存储(称之为线程私有的内存)。
当线程正执行 Java 程序时:程序计数器 记录正在执行的虚拟机字节指令地址。
执行 native
方法,计数器值为空 undefined
;
该内存区域是唯一一个 Java 虚拟机规范中没有规定任何 OutOfMemoryError
情况的内存区域;
这块内存区域是线程私有的。
虚拟机栈
虚拟机栈是方法的工作空间,由一个一个的栈帧组成,栈帧是在每一个方法调用时产生的。
每一个栈帧由局部变量区
、操作数栈
等组成。每创建一个栈帧压栈,当一个方法执行完毕之后则出栈。
- 如果出现方法递归调用出现死循环的话就会造成栈帧过多,最终会抛出
StackOverflowError
。- 若线程执行过程中栈帧大小超出虚拟机栈限制,则会抛出
StackOverFlowError
。- 若虚拟机栈允许动态扩展,但在尝试扩展时内存不足,或者在为一个新线程初始化新的虚拟机栈时申请不到足够的内存,则会抛出
OutOfMemoryError
。
这块内存区域也是线程私有的。
Java 堆
Java 堆是整个虚拟机所管理的最大内存区域,所有的对象创建都是在这个区域进行内存分配。
堆区包括属性空间和方法空间,属性的类型决定开辟空间大小,属性个数决定开辟空间数量,堆中存放方法引用的空间大小为4个字节
这块区域也是垃圾回收器重点管理的区域,由于大多数垃圾回收器都采用分代回收算法
,所有堆内存也分为 新生代(Young)
、老年代(Old)
,可以方便垃圾的准确回收。
新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。
新生代:Young Generation
,主要用来存放新生的对象。
老年代:Old Generation
或者称作 Tenured Generation
,主要存放应用程序声明周期长的内存对象。
这块内存属于线程共享区域。
方法区
在 Sun JDK 中这块区域对应的为 PermanetGeneration ,又称为持久代,方法区是堆的逻辑部分。
方法区主要用于存放已经被虚拟机加载的类信息,如常量
,静态变量
。
这块区域也被称为永久代
。
由于持久代内可能会发生内存泄露或溢出等问题而导致的 java.lang.OutOfMemoryError: PermGen
,JEP小组从 JDK1.7
开始就筹划移除持久代(JEP 122: Remove the Permanent Generation),并且在 JDK 1.7
中把字符串常量,符号引用等移出了持久代。到了 Java 8
,持久代被彻底地移出了 JVM
,取而代之的是元空间(Metaspace
):
In JDK 8, classes metadata is now stored in the native heap and this space is called Metaspace.
所以从Java 8开始,方法区被移至 Metaspace 内。
这块内存属于线程共享区域。
运行时常量池
运行时常量池是class文件中每一个类或接口的常量池表的运行时表示形式,是方法区的一部分。
它包括了若干种不同的常量。常量池表存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。运行时常量池具有动态性,运行期间也可以将新的量放到运行时常量池中,典型的应用是String类的intern方法:
public native String intern()
JDK 1.7
开始,字符串常量和符号引用等就被移出持久代
:
- 符号引用迁移至系统堆内存(
Native Heap
) - 字符串字面量迁移至Java堆(
Java Heap
)