JVM 内存基础概念之 Java 堆,jvmjava
JVM 内存基础概念之 Java 堆,jvmjava
前言
在上篇文章中,我们了解了 Java 虚拟机栈、本地方法栈 以及 程序计数器。这篇再来了解下 Java 堆。
Java 堆
Java 堆是被所有线程所共享的一块区域,在 Java 虚拟机创建的时候创建,在 Java 虚拟机退出的时候销毁。
通常情况下,Java 堆是 Java 虚拟机中最大的一块内存区域。其作用就是作为 Java 对象的主要存储区域。
在《Java 虚拟机规范》中明确要求该区域是需要实现自动内存管理的,也就是 GC。但是并没有限制说非得用哪种算法和技术去实现。
由于 Java 堆是垃圾回收器回收的主要区域,因此 Java 堆在很多时候也被称为 GC 堆。
由于 Java 堆是被所有线程所共享的,那么如果有多个线程同时去用 Java 堆中的一块内存去存储对象,那么岂不是乱套了? 为了避免各种线程间的竞争关系,Java 堆的实现很可能划分出多个线程私有的分配缓冲区。这一类缓冲区被被称为 TLAB(Thread Local Allocation Buffer)。 这时候各个线程会在各自独立的 TLAB 之中分配对象,仅当 TLAB 的空间用完的时候才会加锁,并且向 Java 堆分配新的 TLAB 空间内存。
Java 堆的内存控件还有一种更常见的划分方式,比如说:新生代、老年代、永久代。这种划分是基于内存回收的经典算法:分代算法来进行划分的。
根据 Java 虚拟机规范的规定,Java 堆可以在物理上不连续之间的空间上分配,只要逻辑上看起来是连续的就可以,这有点像我们的分配磁盘空间。在实现时 Java 堆可以实现为固定大小的,也可以实现为可动态扩展的,在当前的主流虚拟机中,都是按照可动态扩展来实现的。
堆和栈
下面我们通过实例来了解下 Java 堆与 Java 虚拟机栈是怎么配合工作的。首先来看下面这行代码:
Object obj = new Object();
代码非常简单,就创建了一个 obj 对象。对于编译器而言,它可以通过我们定义的一个叫做 obj 的局部变量了解到在 Java 栈的本地变量表中,必须有一个 Slot 区域来容纳这个 obj 对象所代表的引用。
所以,在这行代码左边的部分 Object obj 来说,对于编译器的影响是:它会在 Java 虚拟机栈的本地变量表中预留一个 Slot。 对于代码右边的部分 new Object() 来说,当代码执行到这一行来时,它会在 Java 堆中产生一个 Object 变量,并且会把这个 Object 变量的 reference 赋值到 Java 虚拟机栈本地变量表中 obj 对象对对应的 Slot 中,这个 Slot 必须是存储 reference 类型数据的。
简单总结下上面的话大概就是,编译期会在 Java 虚拟机栈中的本地变量表中预留出 reference 类型的 Slot,但是这时这个 Slot 还是空的。在运行期执行到该行代码时 Java 堆中会创建对象的实例数据,然后把对象的地址引用存放到之前预留的 Slot 中。
通过在局部变量表小节中对 reference 的说明,我们知道一个 reference 类型的数据,应该能完成两件事情,一是可以通过这个 reference 直接或者间接的查找到这个 Java 对象存放在 Java 堆之中的实例数据地址。二是可以通过这个 reference 直接或者间接的查找到这个对象所属的数据类型在 Java 方法区之中的类型信息。
为了完成这两件事情,对于 Java 虚拟机的实现来说,一般会有两种典型的内存布局可以选择。 第一种可选择的方式是:reference 直接指向对象的实例数据的地址,我们可以通过这个 reference 直接在 Java 堆中找到这个对象的实例数据。然后,在这个对象的对象头之中,预留一块指针,让虚拟机可以通过这个指针到方法区中找到这个对象所属的类型。如下图:
另外还有一种经典的内存布局就是,reference 并不直接指向 Java 堆中的对象实例数据。而是通过一个“中间人”,也就是句柄池,句柄池是在 Java 堆当中的,一个句柄同时拥有在实例池中对象实例数据的指针,也拥有在方法区中对象类型数据的指针。如下图:
这两种内存布局各有千秋,使用句柄池方式最大的好处是,在 reference 中存储的是一个稳定的句柄地址,因为就算你对象移动了(在进行内存回收时,对象移动是很普遍的),那么改变的也只是句柄池中对象实例数据的指针,而 reference 是不需要修改的。
对于 reference 直接指向对象实例数据的方式来说,最大的好处就是速度会更快,因为少了“中间人”(句柄池)这个环节,节省了一次指针定位的开销。因为对象的使用很频繁,所以当这种开销积少成多时,也是一种很可观的成本。
对于本文中所讲的 HotSpot 虚拟机而言,它使用的是后一种,也就是直接指向的方式。
Java 堆中的异常
Java 堆中可能会发生如下异常:
- OutOfMemoryError:当实际所需要的堆大小超出了自动管理内存系统所能提供的最大容量,那么 JVM 将会抛出该异常。
代码演示:
public class OutOfMemoryErrorTest {
public static void main(String[] args){
ArrayList<OutOfMemoryErrorTest> list = new ArrayList<>();
while (true){
list.add(new OutOfMemoryErrorTest());
}
}
}
接下来的文章,我们再来了解下方法区以及运行时常量池。
相关文章
- 暂无相关文章
用户点评