JVM系列——JAVA内存区域与内存溢出异常

最近更新的两篇String分析的文章,其实都是学习JAVA虚拟机时候遇到的一些问题总结,刚把《深入理解JAVA虚拟机》第二章看完,这篇文章将会介绍一下JAVA内存区域中常见 的一些问题。

1.运行时的数据区域

image

程序计数器

  • 程序计数器(Program Counter Register) 是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
  • 每条线程都有一个独立的程序计数器。(为了方便线程切换)
  • 此内存区域是唯一一个在JAVA虚拟机规范中没有规定任何OutOfMemory情况的区域。

JAVA虚拟机栈

  • 同样是线程私有的
  • 每个方法在执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接等信息。
  • 局部变量表存放了分钟编译期可知的基本数据类型(char、int、float...)以及对象引用(对象的存放地址,而不是对象本身
  • 当线程请求的栈深度大于虚拟机所允许的深度(深度可通过向JVM传参设置),将抛出StackOverFlowError
  • 如果扩展时(递归)无法申请到足够的内存,就会抛出OutOfMemoryError。

本地方法栈

  • 与虚拟机栈类似,不同的是本地方法栈使用到的是Native方法服务。
  • 在HotSpot(SUN公司的JVM)中,已经将虚拟机栈和本地方法栈合并了

JAVA堆

  • JVM所管理的区域中内存最大的一块。
  • 是所有线程共享的一块区域
  • 唯一目的就是存放对象实例,所有的对象实例以及数组都要在堆上分配。
  • 堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
  • 无法申请到足够的内存,就会抛出OutOfMemoryError。

方法区

  • 存储已被虚拟机加载的类的信息、常量、静态变量等
  • 无法申请到足够的内存,就会抛出OutOfMemoryError。

运行时常量池

  • 是方法区的一部分
  • 存放编译期生成的各种字面量和符号引用(显示声明的字符串就是在编译期被放在这里的

2. HotSpot虚拟机对象

对象的创建过程

  • 遇到new指令后,首先检查常量池中能否定位到这个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。若没有则必须先执行相应的类加载过程。
  • 检查完毕后,开始为新的对象分配内存,对象所需内存的大小在加载完成后便可以完全确定。为对象分配空间的任务等同于把一块确定大小的内存从堆中分出。
  • 内存分配完毕后,虚拟机需要将分配到的内存空间都初始化为0
  • 对对象进行相应的设置。比如对象是哪个类的实例、如何找到其元数据等。

对象的内存布局

对象在内存中的布局可以分为3块区域:对象头、实例数据和对齐填充。

对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,另一部分则为类型指针。如果对象是一个数组,则对象头中还必须有一块用于记录数组长度的数据。

对象的访问定位

为了使用对象,需要通过栈上的对象引用数据来对对象实例进行访问。主流的访问方式有两种:使用句柄和直接指针。

  • 如果使用句柄访问的话,那么JAVA堆中将分出一块内存来作为句柄池,栈上存储的就是对象的句柄池的句柄地址,也就是说栈需要通过这种“代理”的方式进行对象访问。
  • 直接访问,就是在栈中直接存放实例所在的地址。


对比:直接访问的优势就是速度快,而句柄访问的优势就是栈中的引用存储稳定(一旦实例被移动,只会改句柄池中的实例数据指针)。Sun的HotSpot目前采用的是直接访问方式。

你可能感兴趣的:(JVM系列——JAVA内存区域与内存溢出异常)