转载请以链接形式标明出处:
本文出自:103style的博客
JAVA运行时数据区域
主要包括:
线程共享 的数据区:方法区、堆。
线程私有 的数据区:程序计数器、虚拟机栈、本地方法栈。
如下图:
程序计数器
程序计数器 Program Counter Register
是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条执行字节码指令。
由于Java虚拟机是通过线程轮流切换并分配处理器执行时间的方式来执行一条线程的指令。所以为了线程切换后能恢复到正确位置,每条线程都有一个独立的程序计数器。 各线程之间的程序计数器互不影响,独立存储,我们称这类内存为 线程私有 的内存。
如果执行的是 Java
方法,这个计数器记录的是正在执行的虚拟机字节码指令地址。如果是 native
方法,计数器为空。此内存区域是唯一一个在java虚拟机规范中没有规定任何 OutOfMemoryError
情况的区域。
Java虚拟机栈
与 程序计数器 一样,Java虚拟机栈 也是线程私有,生命周期 和线程相同。
Java虚拟机栈 描述的是 Java 方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
一个方法从开始到结束对应一个栈帧入栈到出栈的过程。
局部变量表存放了各种基本类型、对象引用和 returnAddress
类型(指向了一条字节码指令地址)。
其中 64
位长度 long
和 double
占 两个
局部变量空间Slot
,其他只占 一个
。
局部变量表的内存空间在编译期间完成分配,所以在运行期间不会改变其大小。
Java虚拟机栈 规定的异常情况有两种:
- 线程请求的栈的深度大于虚拟机所允许的深度,将抛出
StackOverflowError
异常. - 如果虚拟机可以动态扩展,如果扩展时无法申请到足够的内存,就抛出
OutOfMemoryError
异常.
本地方法栈
和 Java虚拟机栈 很类似,不同的是 本地方法栈 为 Native
方法服务。
有的虚拟机(eq: Sun HotSpot )
则把 Java虚拟机栈 和 本地方法栈 合二为一。
Java堆
对大多数应用来说,
Java堆是Java虚拟机所管理的内存中最大的一块。由所有线程共享,在虚拟机启动时创建。堆区唯一目的就是存放对象实例。
Java堆 是 垃圾收集器 管理 的 主要区域 ,也称 GC堆 。
从内存回收角度来看,由于现在的收集器都采用 分代收集算法,Java堆 可以细分为 新生代 和 老年代。再细分可分为 Eden空间、From Survivor空间、To Survivor空间。
Java堆 可以处于物理上不连续的内存空间,只要逻辑上连续即可。
在实现时,既可以是固定大小,也可以是可扩展的。
目前主流的虚拟机都是可扩展的,通过 -Xmx 和 -Xms 控制。堆无法扩展时,抛出 OutOfMemoryError
异常.
方法区
方法区 是所有线程共享的内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
虽然 Java虚拟机规范 把 方法区 描述为 堆的一个逻辑区域,但是它有一个别名叫做 Non-Heap
(非堆),目的应是区分与Java堆 。
对于在 HotSpot 上开发的开发者来说,愿意把 方法区 称为 永久代,本质上两者并不等价,仅是因为 HotSpot 把 GC分代收集算法 扩展到 方法区,或者说使用 永久代 来实现 方法区 而已。
Java虚拟机规范 对 方法区 的限制非常宽松,除了和 Java堆 一样 不需要连续的内存区域 和 可以选择固定大小或者可扩展 外,还可以选择 不实现垃圾收集。 相对而已,在这个区域的垃圾收集行为较少出现。
当 方法区 无法满足内存分配需求时,抛出 OutOfMemoryError
.
运行时常量池
运行时常量池 是 方法区 的一部分,Class
文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项是常量池Constant Pool Table
,用于存放编译期生成的各种字面量和符号引用。这部分将在 类加载后 进入 方法区 的 运行时常量池。
运行时常量池 相对于 Class
文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生。运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是 String
类的 intern()
方法。
既然运行时常量池 是 方法区 的一部分,自然受到 方法区 内存的限制,无法满足内存分配需求时,抛出OutOfMemoryError
.
直接内存
直接内存 并不是虚拟机运行时数据区的一部分,也不是 Java虚拟机规范 中定义的内存区域。
但是这部分内存也被频繁的使用,而且也可能导致 OutOfMemoryError
。
JDK1.4 加入了 NIO
,引入一种基于 通道Channel
与 缓冲区Buffer
的I/O方式,它可以使用 Native函数库
直接分配堆外内存,然后通过一个存储在Java堆中的 DirectByteBuffer
对象作为这块内存的引用进行操作。因为避免了在 Java堆和Native堆中来回复制数据,提高了性能。
显然 直接内存 的分配不会受到 Java堆 大小的限制,但是会受到本机总内存的限制,当各个内存区域总和大于物理内存限制,抛出 OutOfMemoryError
异常。
如果觉得不错的话,请帮忙点个赞呗。
以上
扫描下面的二维码,关注我的公众号 Android1024, 点关注,不迷路。