[JVM] Java 内存区域与内存溢出异常

笔者将从概念上介绍 Java 虚拟机内存的各个区域,讲解这些区域的作用、服务对象以及其中可能产生的问题

运行时数据区域

运行时数据区域主要有:

  • 方法区 Method Area
  • 堆 Java Heap
  • 虚拟机栈
  • 本地方法栈
  • 程序计数器

其中方法区和堆由所有线程共享,其余为线程内部隔离的数据区域。

[JVM] Java 内存区域与内存溢出异常_jvm

程序计数器

一块较小的内存区域,可以看做是当前线程所执行的字节码的行号指示器。

由于多线程的切换时通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(内核)都只会执行一条线程中的指令。为了线程切换后能恢复到正确的执行位置,各线程间必须各自拥有一个独立的程序计数器,即线程私有内存。

另外,执行Java方法的时候,计数器记录的是正在执行的虚拟机字节码指令的地址;而执行的是Native方法,记录的值为空。此内存区域是Java虚拟机规范中唯一一个没有规定任何OutOfMemoryError情况的区域。

Java 虚拟机栈

虚拟机栈描述的是Java方法执行的内存模型。和程序计数器一样,也是线程私有的。每个方法在执行的同时会创建一个栈帧

栈帧用于存储:局部变量表、操作数栈、动态链表、方法出口灯信息。每个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。

局部变量表用于存放编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,不等同于对象本身,可能是一个指向对象的起始地址的引用指针,也可能是个指向一个对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)

该区域根据Java虚拟机规范中,定义了两种异常情况:

  • 线程请求的栈深度大于虚拟机匀速的深度,将抛出StackOverflowError
  • 如果虚拟机栈可以动态扩展,但是扩展时无法申请到足够内存,将会抛出OutOfMemoryError

本地方法栈

它的作用与虚拟机栈相似,区别在于,前者为虚拟机栈提供Native方法服务,后者为虚拟机执行Java方法(字节码)服务。

该区域和虚拟机栈抛出的异常情况一样。

Java 堆

Java 堆是Java虚拟机所管理的内存中最大的一块。被所有线程共享的一块区域,几乎所有的对象实例都在这里分配内存。也是垃圾收集器管理的主要区域,因此也被称为GC堆

从内存回收的角度,可以细分为:新生代和老年代;更细致些的划分:Eden空间、From Survivor空间、To Survivor空间。

从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程的私有分配缓冲区(Thread Local Allocation Buffer,TLAB)。

根据Java虚拟机规范的规定,Java堆可以在物理上处于不连续的内存空间中,只要逻辑上连续就可以了。在实现时,可以实现为固定大小的,也可以是可扩展的。

一般都是可扩展的,通过参数进行配置:-Xmx-Xms

如果在堆中没有内存完成实例分配,并且堆也无法在扩展时,将会抛出OutOfMemoryError

方法区

方法区和Java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机栈加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

方法区在Java虚拟机规范中被描述为一个逻辑部分,但是它有个别名叫做Non-Heap非堆,目的就是和Java堆区分开。

该区域垃圾回收的主要目标是:针对常量池的回收和类型的卸载。

根据Java虚拟机规范,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError.

运行时常量池

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

既然属于方法区的一部分,异常抛出和方法区一致。

直接内存

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是这部分却被频繁使用,也可能会导致OutOfMemoryError出现。

JDK1.4后引入的NIO,引入了一中基于通道与缓冲区的I/O方式,可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样做是为了在一些场景中提高性能,避免Java堆和Native堆中来回复制数据。

但是,该内存的分配会受到本地内存总量的限制,服务器管理员在配置虚拟机参数时,可以根据实际内存设置-Xmx等参数信息来调整堆的内存容量,来控制直接内存可以分配的最大容量。

REFERENCES

《深入理解Java虚拟机-JVM高级特性与最佳实践》读书笔记