Java虚拟机的内存区域

时间:2022-12-27 12:52:56

最近复习Java知识,记录一下程序运行时Java内存分配的原理,通过对对象的放置、内存的分配进一步理解程序的运行机制:

一、从概念上划分(《Java编程思想》上的划分)

1、寄存器:最快的存储区,位置处理器内部,但其数量极其有限,所以寄存器应该根据需要进行分配,但程序不能直接控制,所以不会在程序中感觉到寄存器的存在,(C和C++中允许程序员向编译器建议寄存器的分配方式)

2、栈:位于通用RAM(随机访问存储器)中,保存基本变量(成员变量、局部变量)和引用(包括:基本数据类型的值类的实例也称对象的引用加载方法时的帧。通过堆栈指针可以从处理器那里获得直接支持(堆栈指针向下移动分配新内存,向上移动释放内存),这种分配存储方式快速有效、仅次于寄存器,但创建程序时Java系统必须知道存储在堆栈内所有项的确切生命周期,以便上下移动堆栈指针(这一点体现了程序的灵活性)

3、堆:位于通用的内存池(也位于RAM区),用于存放所有Java对象(类实例化对象、数组)(注意创建出来的对象只包含属于各自对象的成员变量,并不包含成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但他们共享各自的成员变量,而不是每创建一个对象就把成员方法复制一次)。堆相比于堆栈的好处是编译器不需要知道存储在堆里的数据存活时间,因此堆里分配存储有很大的灵活性(只需要new一下就会自动在堆里进行存储分配),代价是堆进行存储分配和清理可能比用堆栈进行存储分配需要更多的时间(如果地区上课以在Java中你在C++中一样在栈中创建对象)

4、常量存储:常量值通常直接存放在程序代码内部(因为它永远不会改变),用于存放常量、字符串以及静态区的东西等等

5、非RAM存储:数据完全存活于程序之外,即不受程序任何控制,在程序没有运行时也可以存在。两个基本例子:流对象对象转化成字节流发送给另一台机器)和持久化对象对象存储于磁盘上),这种存储方式的技巧在于可以将对象转化成可以存放在其他媒介上的事物,需要时可以恢复成常规的、基于RAM的对象


二、从功能上划分(《深入理解Java虚拟机》上的划分)

1、程序计数器:(每个线程需要一个独立的程序计数器)当前线程所执行的字节码的行号指示器。字节码解释器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成

如果线程正在执行一个Java方法,则计数器是正在执行的虚拟机字节码指令的地址;如果正在执行一个Native方法,则计数器值为空(Undefined)。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域

2、Java虚拟机栈:(线程私有)方法执行时会创建栈桢(用于存储局部变量表、操作数栈、动态链接、方法出口等信息)。一个方法从调用到执行完成,对应一个栈桢在虚拟机栈中入栈到出栈的过程

该内存区域存在两种异常:(1)线程请求栈的深度大于虚拟机所允许的深度---*Error;(2)虚拟机可动态扩展,但扩展时无法申请到足够的内存---OutOfMemoryError

3、本地方法栈:(线程私有)对应Java虚拟机栈(为虚拟机执行Java方法服务),而本地方法栈(为虚拟机使用到的Native方法服务)

4、Java堆:(所有线程共享一块内存区域)存放对象实例。Java堆可以处于物理上不连续而逻辑上连续的空间。

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

5、方法区:(所有线程共享一块内存区域)存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

当方法区无法满足内存分配需求时将抛出OutOfMemoryError异常

6、运行时常量池:(方法区的一部分)用于存放编译期生成的各种量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放

当常量池无法再申请到内存时会抛出OutOfMemoryError异常

7、直接内存:(不是虚拟机运行时数据区的一部分,也不Java虚拟机规范中定义的内存区域)在JDK1.4后新增的NIO(New Input/Output)类,引入了基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一处存储在Java堆中的DrectByteBuffer对象偢这块内存的引用进行操作

本机直接内存的分配不会受Java堆大小限制,但会受本机总内存以处理器建起空间的限制。所以当各个内存区域总和大于物理内存限制时会导致动态扩展时出现OutOfMemoryError异常