JAVA内存区域与内存溢出异常

JAVA内存区域与内存溢出异常

JAVA虚拟机管理下的内存包括如下几个运行时数据区域

  • 程序计数器: 一块较小的内存空间,是当前线程所执行的字节码的行号指示器

    每个线程都有独立的一个程序计数器,使得各个线程的切换可以恢复到正确的执行位置

  • JAVA虚拟机栈:描述的是JAVA方法执行的内存模型
    • 每个方法在执行的同时都会创建一个栈帧(存储方法的相关信息)
    • 一个方法从调用到执行的过程中就相当于一个栈帧在虚拟机栈中的入栈和出栈的过程
    • 许多人喜欢把JAVA内存分为堆内存和栈内存(当然这不是研究的分法,只是与对象内存分配关系最密切的内存区域就是这两块),其中栈内存指的就是虚拟机栈中的局部变量表部分
    • 虚拟机栈的局部变量表存放编译期可知的各种基本数据类型,对象引用以及returnAddress类型
    • 这里指的注意的是:局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,这个方法运行期间不会改变局部变量表的大小
    • 对于上一点,在JVM规范中定义了两种异常状况:① 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError的错误;② 如果虚拟机栈可以动态扩展,只是扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常.
  • 本独方法栈: 与虚拟机栈的作用类似, 区别在于本地方法栈为虚拟机使用到的Native方法服务
    • 该栈也会抛出StackOverflowError与OutOfMemoryError异常
  • JAVA堆: 存放对象实例,被所有线程共享
    • JVM所管理的内存中最大的一块
    • JAVA堆是垃圾收集器管理的主要区域, 很多时候被称作为”GC堆”
  • 方法区
    • 用于存储已被虚拟机加载的类方法,常量,静态变量,即时编译器编译后的代码等数据
  • 运行时常量池
    • 方法区的一部分,Class文件除了描述信息之外,还有一个常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池存放
    • 具备动态性,不要求常量一定是在编译器中产生,也可以在运行期间将新的常量放入池中, 如String类中的intern()方法
    • 内存饱满是也会抛出OutOfMemoryError方法
  • 直接内存

HotSpot虚拟机对象

对象的创建

  • 对象创建 (在语言层面上,创建对象通常相当于一个new关键字)
    • 当虚拟机遇到一条new指令时,首先会去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载或解析或初始化过,如果没有,那必须先执行相应的类加载过程
    • 检查通过后,虚拟机为新生对象分配内存,对象所需要的内存的大小在类加载完成后便可完全确定
    • 为对象分配内存就相当于把一块确定大小的内存从JAVA堆中划分出来
  • 执行完new指令后,接着执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来

对象的内存布局

  • 在HotSpot虚拟机中,对象在内存中存储的布局可分为三块区域:对象头,实例数据和对齐填充
  • 对象头包括两部分信息:① 存储对象自身的运行时数据, ② 类型指针(指向类元数据的指针, 虚拟机通过这个指针来确定这个对象是哪个类的实例, 如果对象是数组, 则对象头中还必须有一块用于记录数组长度的数据)
  • 实例数据:对象头真正存储的有效信息,也是代码中所定义的各种类型的字段内容