运行时数据区主要包括:方法区,堆,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)