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

计数器

  1. 字节码解释器工作时通过改变计数器的值来选取下一条需要执行的字节码指令。
  2. 每个线程有独立的计数器。
  3. 如果正在执行的是Native方法,这个计数器值为空(Undefined)。
  4. 计数器的内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

虚拟机栈

  1. 与计数器一样,虚拟机栈也是线程私有的,生命周期与线程相同。

  2. 描述的是Java方法执行的内存模型

  3. 栈帧是方法运行时的基础数据结构。

  4. Java虚拟机规范对这个区域规定了两种异常情况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

    本地方法栈

  5. 为虚拟机使用到的Native方法服务。

  6. 本地方法栈也会抛出StackOverflowError和OutOfMemory异常。

  7. 与虚拟机栈的作用相似。

Java堆

  1. Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。以为目的是存放对象实例。
  2. 是垃圾收集器管理的主要区域。
  3. 堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

方法区

  1. 也是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  2. 除了和java堆一样不需要连续的内存和可以选择规定大小或者可扩展外,还可以选择不实现垃圾收集。
  3. 这区域的内存回收目标主要是针对常量池的回收和对类型的卸载。
  4. 无法满足内存分配的需求时,将会抛出OutOfMemoryError异常。

运行时常量池

  1. 是方法区一部分。
  2. 常量池用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池存放。

直接内存

  1. NIO

对象的创建

  1. 对象在堆上的内存分配方式分为指针碰撞和空闲列表。
  2. 对象内存空间分配完成后,内存空间都初始化为零值。

对象的内存布局

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

对象的定位

  1. 通过栈上的reference数据来操作堆上的具体数据。
  2. 目前主流的访问方式有使用句柄和直接指针两种。

排错

  1. 注意区分是内存泄露(Memory Leak),还是内存溢出(Memory Overflow)
  2. 使用Eclipse Memory Analyzer进行分析,安装教程:http://www.jianshu.com/p/3b3c3a914724
堆溢出
  1. 堆空间中的每个对象都是由一个根元素为起点被层层调用的。因此,一个对象还被某一个存活的根元素所引用,就会被认为是存活对象,不能被回收,进行内存释放。因此,我们可以通过分析一个对象到根元素的引用路径来分析为什么该对象不能被顺利回收。如果说一个对象已经不被任何程序逻辑所需要但是还存在被根元素引用的情况,我们可以说这里存在内存泄露。
  2. -Xmx20m -Xms20m 设置堆的最大最小;-Xss128k 设置虚拟机栈的大小;-Xoss设置本地方法栈大小;MaxPermSize(最大方法区容量)
虚拟机栈和本地方法栈溢出
  1. 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflow异常(栈溢出);如果虚拟机在扩展栈时无法申请到足够的内存空间,则将抛出OutOfMemoryError异常(内存泄露)。
  2. 为了防止多线程导致的内存溢出,要注意控制虚拟机栈和本地方法栈的大小。
方法区和运行时常量池溢出

运行时常量区是方法区的一部分。

  1. String.intern()方法在JDK1.7之后的改变,注意“首次出现原则”。
本机直接内存溢出
  1. DirectMemory容量可通过 -XX: MaxDirectMemorySize指定,如果不指定则默认与Java堆最大值(-Xmx指定)一样。
  2. 由DirectMemory导致的内存溢出,一个明显的特征是在Heap Dump文件中是不会看见明显的异常,如果读者返现OOM之后Dump文件很小,而程序中有直接或间接使用了NIO,那就可以考虑检查一下是不是这方面的原因。