《深入理解Java虚拟机》读书笔记(1)---第2章 Java内存区域与内存溢出异常

时间:2023-02-15 11:54:14

第2章 Java内存区域与内存溢出异常

 

2.2运行时数据区域

 

Java虚拟机所管理的内存包括以下几个运行时数据区域:方法区、堆区、虚拟机栈、本地方法栈、程序计数器。

 

程序计数器(ProgramCounter Register):用于保存当前线程执行的内存地址。由于JVM程序是多线程执行的(线程轮流切换),所以为了保证线程切换回来后,还能恢复到原先状态,就需要一个独立的计数器,记录之前中断的地方,可见程序计数器也是线程私有的。程序计数器是唯一一个在JVM规范中没有规定任何OutOfMemoryError情况的区域。

 

虚拟机栈(Java Virtual Machine Stacks):线程私有。每当创建一个线程时,JVM就会为这个线程创建一个对应的JVM栈。在这个栈中又会包含多个栈帧,每运行一个方法就创建一个栈帧,用于存储方法中的局部变量表、操作栈、动态链接、方法出口信息。每一个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。

 

本地方法栈(Native Method Stacks):线程私有。其作用与虚拟机栈相似,区别是虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈为虚拟机使用到的Native方法服务。本地方法栈也会抛出*ErrorOutOfMemoryError异常。

 

Java(Java Heap):是Java虚拟机管理的最大的一块内存区域,被所有线程共享,在虚拟机启动时创建。存储对象实例以及数组。这块是GC的主要区域因而也被称为GC)。可以扩展内存空间(通过-Xmx-Xms控制)。当堆中无内存来完成实例分配且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

 

方法区(Method Area):线程共享。存储已被虚拟机加载的类信息、常量、静态变量、构造函数等数据。GC行为在这个区域是比较少出现的。同样,当方法区无法满足内存分配需求时,也会抛出OutOfMemoryError异常。方法区还包含一个运行时常量池。

 

运行时常量池(Runtime Constant Pool):顾名思义,运行时用来存放常量池的,是方法区的一部分,线程共享。当常量池无法再申请到足够的内存时,也会抛出OutOfMemoryError异常。

 

直接内存(Direct Memory)并不是Java虚拟机运行时数据区的一部分。但这部分内存被频繁使用,也可能导致OutOfMemoryError异常出现。属于堆外内存,其分配不受Java堆大小的限制。在JDK1.4中引入了NIO类,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用来进行操作。这避免了在Java堆和Native堆中来回赋值数据,能在一些场景中显著提高性能。

 

运行时数据区域的简单示意图如下:

 

 《深入理解Java虚拟机》读书笔记(1)---第2章 Java内存区域与内存溢出异常《深入理解Java虚拟机》读书笔记(1)---第2章 Java内存区域与内存溢出异常

 

 

2.3对象访问

 

对于形如:

Object obj = new Object();

的代码,Object obj作为一个reference类型的数据在Java栈的本地变量表中,而new Object() 则会反映到Java堆中。同时,Java堆中还必须含有能查到对象类型数据的地址信息,这些类型数据存储在方法区中。

 

由于reference类型在Java虚拟机规范里面只规定了一个指向对象的引用,定位方式不确定。目前主流的访问方式有两种:使用句柄直接指针

 

使用句柄访问Java堆中划分出一块内存作为句柄池,reference存放对象的句柄地址,而句柄中包含了对象实例数据和类型数据的具体地址信息。

 《深入理解Java虚拟机》读书笔记(1)---第2章 Java内存区域与内存溢出异常《深入理解Java虚拟机》读书笔记(1)---第2章 Java内存区域与内存溢出异常

 

直接指针访问Java堆对象的布局中再放置类型数据相关信息的引用,reference直接存储对象地址。

 《深入理解Java虚拟机》读书笔记(1)---第2章 Java内存区域与内存溢出异常《深入理解Java虚拟机》读书笔记(1)---第2章 Java内存区域与内存溢出异常

 

两种访问方式各具优势

使用句柄访问时,reference中存储的事稳定的句柄地址,在对象被移动时值改变句柄中的指针,而不需要修改reference本身;

始终直接指针访问时,速度更快,它节省了一次指针定位的时间开销。因为对象访问在Java中非常频繁,这类开销积少成多也是一项可观的执行成本。