读书笔记-Java内存区域划分

有幸拜读周志明老师的著作《深入理解Java虚拟机:JVM高级特性与最佳实践》,是这本书让我对Java虚拟机有了比较全面的认识,这里记录下一些重点,以备后期查阅。其实买这本书已经有几年了,期间断断续续看了几章,现在终于可以抽出时间来系统读一遍了。话不多说,进入正题。

  • 运行时数据区域

Java虚拟机在执行Java程序时会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的则是依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范(第2版)》的规定,Java虚拟机所管理的内存将会包含以下几个运行时数据区域。

 


 接下来看看每个数据区域的用途:

 

  1. 程序计数器:从上图可以看出程序计数器是线程私有的,会随线程启动而创建,随线程结束而销毁。程序计数器用于记录线程中指令的执行位置(虚拟机字节码指令地址),字节码解释器工作时就是通过改变这个计数的值来选取下一条需要执行的字节码指令。Java虚拟机的的多线程通过线程轮流切换并分配处理时间片实现,所以在任何时刻一个处理器(对于多核处理器来说是一个内核)只会执行一条线程的指令。因此为了线程切换后能恢复到正确的执行位置,每条线程都必须有一个独立的程序计数器。
  2. Java虚拟机栈:也是线程私有,随线程启动和结束而创建和销毁,用于描述方法执行的内存模型,每个方法被执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法调用到执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。局部变量表存储了编译期可知的各种基本数据类型(Java中的8种基本数据类型)及对象引用和returnAddress类型(指向了一条字节码指令的地址)。虚拟栈区域中会有两种异常:StackOverflowError、OutOfMemoryError。
  3. 本地方法栈:线程私有,与虚拟机栈发挥的作用非常相似,区别不过是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈为虚拟机使用到的Native方法服务。SUN的HotSpot直接把Java虚拟机栈和本地方法栈合二为一。这个区域也会抛出StackOverflowError和OutOfMemoryError异常。
  4. Java堆:虚拟机所管理的内存中最大的一块,从上图中可以看出这块区域是所有线程中共享的,虚拟机启动时创建,用于存放运行时创建的对象实例。Java堆是垃圾收集器管理的主要区域。根据Java虚拟机规范的定义,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续即可。实现时可固定大小也可以是可扩展的(通过-Xms和-Xmx控制)。当堆中没有内存完成 实例分配,并且无法扩展时会抛出OutOfMemory异常。
  5. 方法区:线程间共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。HotSpot把这块定义为永久代(Permanent Generation),其他虚拟可并不存在永久代概念。根据Java虚拟机规范规定,当方法区无法满足内存分配需求时,将会抛出OutOfMemory异常,所以这个区域也存在垃圾回收。
  6. 运行时常量池:方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池。运行时常量池具备动态性,运行期间也可以将新的常量放到运行时常量池中(典型案例String.intern()方法)。无法申请到内存时也会抛出OutOfMemoryError异常。
  7. 直接内存:并不是虚拟机运行时数据区域一部分,也不是Java虚拟机规范中定义的内存区域,该区域的产生是由于NIO引入了一种基于通道与缓冲区的IO方式,它可以使用Native方法直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。以避免在Java堆和Native堆中来回复制数据。也可能导致OutOfMemory,其原因可能是受物理内存限制。
  • 对象访问

我们看下下面这句代码所使用到的内存区域:

Object obj = new Object();

假设这句代码出现在方法体中,那这行句代码会分配以下空间

  1. Object obj会反映到Java虚拟机栈的本地变量表中,以一个reference类型数据出现。
  2. new Object()会反映到Java堆中,形成一块存储了Obejct类型所有实例数据的结构化内存。
  3. 将包含能查找到此对象类型数据(对象类型、父类、实现接口、方法等)的地址信息存储到方法区中

由于reference类型在Java虚拟中只规定了一个指向对象的引用,并没有规定引用通过何各方式去定位对象实例,所以不同虚拟机对于对象访问方式实现有所不同,主流访问方式有两种:使用句柄和直接指针。

如果使用句柄方式,Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含对象实例数据和类型各自的具体地址信息。如图:

 


 

如果使用直接指针访问方式,Java堆对象的布局中就必须考虑如何旋转访问类型数据相关信息,reference中直接存储的就是对象地址。如图:

扫描二维码关注公众号,回复: 788133 查看本文章

 


 

 这两种对象访问方式各有优势,使用句柄访问方式的最大好处就是reference中存储的是稳定的句柄地址,在对象移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销。SUN的HotSpot采用直接指针方式访问对象。

猜你喜欢

转载自xiep0110.iteye.com/blog/2232205