《深入理解 Java 虚拟机》读后笔记 - 运行时数据区域
Java 虚拟机栈与程序计数器一样,也是线程私有的,它的生命周期与线程相同。
虚拟机栈描述的是 Java 方法执行的线程内存模型:每个方法被执行的时候 Java 虚拟机都会同步创建一个栈帧 (Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表存放了编译期可知的各种 Java 虚拟机基本数据类型(
boolean
、byte
、char
、short
、int
、float
、long
、double
)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)。这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来表示,其中 64 位长度的
long
和double
类型的数据会占用两个变量槽,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。这里说的“大小”是指变量槽的数量。
在《Java 虚拟机规范》中,对这个内存区域规定了两类异常状况:
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出
StackOverflowError
异常如果 Java 虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出
OutOfMemoryError
异常。
[](()3.本地方法栈
本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native) 方法服务。
与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError
和OutOfMemoryError
异常。
[](()4.Java 堆
Java 堆对于 Java 应用程序来说**,是虚拟机所管理的内存中最大的一块**。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建**。此内存区域的唯一目的就是存放对象实例**,Java 世界里几乎所有的对象实例都在这里分配内存。
Java 堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作 GC 堆
如果从分配内存的角度看,所有线程共享的 Java 堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,
TLAB
),以提升对象分配时的效率Java 堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的,这点就像我们用磁盘空间去存储文件一样,并不要求每个文件都连续存放。但对于大对象(典型的如数组对象),多数虚拟机实现出于实现简单、存储高效的考虑,很可能会要求连续的内存空间。
Java 堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的 Java 虚拟机都是按照可扩展来实现的(通过参数-
Xmx
和-Xms
设定)。如果在 Java 堆中没有内存完成实例分配,并且堆也无法再扩展时,Java 虚拟机将会抛出
OutOfMemoryError
异常。
[](()5.方法区
方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 存等数据。
方法区只是一个抽象概念,其具体实现是通过以下 2 种方式:
JDK7 之前:永久代,使用的堆内存
JDK8 之后:元空间,使用的本地内存
具体过程变更过程:
在
JDK 6
的时候 HotSpot 开发团队就有放弃永久代,逐步改为采用本地内存来实现方法区的计划了。到了
JDK 7
的 HotSpot,已经把原本放在永久代的字符串常量池、静态变量等移出。而到了
JDK 8
,终于完全废弃了永久代的概念,改用在本地内存中实现的元空间来代替,把 JDK 7 中永久代还剩余的内容(主要是类型信息)全部移到元空间中。
根据《Java 虚拟机规范》的规定,如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError
异常。
1.8 以前会导致永久代内存溢出
java.lang.OutOfMemoryError: PermGen space
1.8 以后会导致元空间内存溢出
java.lang.OutOfMemoryError: Metaspace
[](()6.运行时常量池
运行时常量池是方法区的一部分。
评论