JVM运行时数据区域

84 阅读6分钟

运行时数据区域

Java虚拟机在执行Java程序的过程汇总会把它管理的内存划分成若干个不同的数据区域。根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域

image.png

1. 程序计数器

又名程序寄存器,PC Register

线程私有

是一块较小的内存

在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令

如果一个Java程序正在执行Native方法,这个计数器的值应该为空(Undefined)

唯一无OOM的区域,不会发生内存溢出(OutOfMemoryError)

2. 虚拟机栈

JVM Stacks

线程私有

它的生命周期和线程相同。每启动一个线程,JVM都会为它分配一个Java虚拟机栈

虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。

本地方法栈是为虚拟机使用到的Native 方法服务。

用于存放方法中的局部变量表,操作数栈,动态连接,方法出口等

可能产生的异常:StackOverflowErrorOutOfMemoryError

局部变量表

存放方法参数和局部变量

存放了编译器可知的JVM基本数据类型对象引用(reference类型)、returnAddress类型(指向了下一条字节码指令的地址)。局部变量表中的存储空间以局部变量槽(Slot)来表示,其中64位长度的数据占两个变量槽(long和double),其余占一个变量槽。一个变量槽占多大的空间是由不同的JVM自己来决定的。

操作数栈

也常被称为操作栈。操作数栈的每一个元素可以是任意Java数据类型,32位的数据类型占用一个栈容量,64位的栈两个栈容量。在方法执行的过程中,根据字节码指令,往操作数栈中写入或提取数据。主要保存计算过程中的中间结果

动态连接

也叫指向运行时常量池的方法引用,动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。

方法出口

当一个方法执行是有两种方法退出该方法:正常完成出口,异常完成出口。无论采用何种方法退出,在方法退出后都需要返回到方法被调用的位置,程序才能继续执行。

3. 本地方法栈

Native Method Stack

线程私有

为虚拟机使用到的Native方法服务。如Java使用C++接口时,代码在此区运行。

可能产生的异常:StackOverflowErrorOutOfMemoryError

4. 堆

Heap

线程共享

垃圾回收的主战场

分代结构:年轻代、年老代

异常:OutOfMemoryError

在Java世界里“几乎”所有对象实例都会在这里分配内存。(随着Java语言的发展,现在已经能看到出现值类型的支持,栈上分配已经在悄然发生)

在《Java虚拟机规范》规定,Java堆可以是处于物理上不连续的内存空间,但在逻辑上他应该被视为连续的。

存放静态变量,数组,对象

Java程序在运行时创建的所有类型对象和数组都存储在堆中。JVM会根据new指令在堆中开辟一个确定类型的对象内存空间。但是堆中开辟对象的空间并没有任何人工指令可以回收,而是通过JVM的垃圾回收器负责回收

其中一个对象的引用可能在整个运行时数据区中的很多地方存在,比如Java栈,堆,方法区等。

堆中对象还应该关联一个对象的锁数据信息以及线程的等待集合(线程等待池) 。这些都是实现Java线程同步机制的基础。

5. 方法区

Method Area

线程共享

存储类元数据(类名、字段、方法字节码等)、运行时常量池、静态变量。

JDK 8前:永久代(PermGen),受JVM固定大小限制,易导致OOM

JDK 8+ :元空间(Metaspace),使用本地内存,默认无上限(可配置)。

用于存储已经被虚拟机加载的类型信息常量静态变量即时编译器遍以后的代码缓存等数据。

在《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,但它有一个别名叫“非堆(Non-Heap)”,目的是与Java堆区分开来。

异常:元空间溢出(如动态生成大量类)

类型信息
  • 该类型的全限定名。如java.io.FileOutputStream
  • 该类型的直接超类的全限定名。如java.io.OutputStream
  • 该类型时类型还是接口
  • 该类型的访问修饰符(public、abstract、final)
  • 任何直接超接口的全限定名的有序列表。如java.io.Closeable,java.io.Flushable
  • 该类型的常量池。比如所有类型(Class)、方法、字段的符号、基本数据类型的直接数值(final)等
  • 字段信息,对类型中声明的每个字段
  • 方法信息
  • 类静态变量,静态变量不是放在堆里的,所以静态属于类,不属于对象
  • 指向ClassLoader类的引用
  • 指向Class的引用
  • 方法表:为了能快速定位到类型中的某个方法,JVM对每个装载的类型都会建立一个方法表,用于存储该类型对象可以调用的方法的直接引用,这些方法就包括从超类继承来的。而这张表与Java动态绑定机制的实现时密切相关的

线程共享

存储已被虚拟机加载的类信息常量静态变量即时编译器编译后的代码等数据

方法区是多线程共享的。也就是当虚拟机实例开始运行程序时,边运行边加载进class文件。不同的Class文件都会提取出不同类型信息存放在方法区中。同样,方法区中不再需要运行的类型信息会被垃圾回收线程丢弃掉。

包括三个部分:class区(存放虚拟机加载后的.class文件等类信息),静态区(存放static修饰的变量),常量池(string和包装类中的一些常量)

常量池

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