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

来源:互联网 发布:淘宝店铺活动方图案例 编辑:程序博客网 时间:2024/05/18 02:36
  Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进来,墙里面的人想出去。
一.运行时数据区域

Java虚拟机在执行Java程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。如下图所示:


1.1 程序计数器

        作用:看作当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等。每个线程都有一个独立的的程序计数器,各条线程之间计数器互不影响。

1.2 Java虚拟机栈

         虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行的同时会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口灯信息。每一个犯法从调用直至完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。

         局部变量表存放了编译期可知的各种基本数据类型(Boolean,byte,char,short,int,float,long,double),对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和return Address类型(指向了一条字节码指令的地址)

1.3 本地方法栈

区别:虚拟机栈为虚拟机执行Java方法服务,本地方法栈为虚拟机使用到的Native方法服务

1.4 Java堆

        作用:所有的对象实例以及数组都要在堆上分配,也是垃圾收集器管理的主要区域

1.5 方法区

        作用:用于存储已被虚拟机加载的类信息,常亮,静态变量,即时编译器编译后的代码等数据。

1.6 运行时常量池

       作用:是方法区的一部分。Class文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行常量池中存放。

1.7 直接内存

       并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域。

二. HotSpot虚拟机对象探秘

      HotSpot虚拟机在Java堆中对象分配、布局、访问的全过程

2.1 对象的创建

  1. 通过new指令去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,检查这个符号引用代表的类是否已被加载、解析、和初始化过。

  2. 在类加载通过后,虚拟机为新生对象分配内存。有两种方式:一、“指针碰撞”:如果java堆中的内存是规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,通过指针向空闲那边挪动一段与对象大小相等的距离。二、“空闲列表”:Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,虚拟机通过维护一个列表,记录内存块,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。

  3. 内存分配完以后,虚拟机需要将分配到的内存空间都初始化为零值。

2.2 对象的内存布局

  1. 对象头:用于存储对象自身的运行时数据.如哈希码、GC分代年龄、锁状态标志等等。
  2. 实例数据:对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。
  3. 对齐填充:占位符的作用。

2.3 对象的访问定位

  1. 句柄访问:通过Java堆划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
  2. 直接指针访问:Java堆对象的布局中就必须考虑如何防止访问类型数据的相关信息,而reference中存储的直接就是对象地址。

三. OutOfMemoryError

  1. Java堆溢出;
  2. 虚拟机栈和本地方法栈溢出;一:如果线程请求的深度大于虚拟机所允许的最大深度,将抛出StackOverflowError;二:如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError;
  3. 方法区和运行时常量池溢出;
  4. 本机直接内存溢出;