0%

《深入理解JVM虚拟机》读书笔记——第2章 Java内存区域

概要

  • 运行时数据区域
  • 对象创建、布局以及定位
  • 各区域OOM分析

比较枯燥,比较干,但没办法,怼吧`

运行时内存分布图

线程私有

  • 程序计数器(字节码行号指示器)
  • 虚拟机栈(Java方法栈)
  • 本地方法栈

线程共享

  • 方法区

区域说明

  1. 程序计数器

    • 很小的一块内存空间
    • 记录当前线程正在执行的字节码指令地址(分支、循环、跳转、异常处理),如果当前在执行native方法,值为空
    • 唯一一个在JVM规范中没有规定OOM情况的区域
  2. Java虚拟机栈
    描述Java方法执行的内存模型:方法执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法从调用到执行完成过程,对应一个栈帧在虚拟机栈中的入栈和出栈的过程。

    通常说的堆内存(heap)和栈内存(Stack),栈内存是指虚拟机栈中的局部变量表部分

    局部变量表存放基本数据类型、对象引用和retrunAdress类型.所需要的内存空间在编译期就可以完成分配。
    在JVM规范中,这个区域有两种异常情况:

    • 请求的栈深度大于虚拟机允许的深度,抛出StackflowError异常
    • 当动态扩展时无法申请到足够的内存,抛出OOM异常
  3. 本地方法栈
    类似Java虚拟机栈,只是适用与Native方法,也会抛出StackflowError和OOM异常。

  4. Java堆

    • 在虚拟机启动时创建,时Java虚拟机所管理的内存中最大的一块。
    • 所有对象实例和数组都要在堆上分配内存。
    • 由于现代垃圾收集器基本都采用分代收集算法,故可以划分为老年代和新生代。
    • 当堆中没有内存完成分配实例,并无法在扩展时,抛出OOM异常
  5. 方法区

    • 存储已被虚拟机加载的类的信息、常量、静态变量、即时编译器编译后的代码
    • JVM规范不限制是否对该区域进行垃圾回收
    • 当无法满足内存分配需求时,抛出OOM
    • 还包含运行时常量池
  6. 运行时常量池
    Class文件出了有类的版本、字段、方法、接口等描述信息外,还有一项是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容在类加载后进入方法区的运行时常量池存放。
    常量池作为方法区的一部分,无法再申请到内存时,抛出OOM

  7. 直接内存
    不属于虚拟机运行时数据区的一部分,也不是JVM规范中定义的内存区域,但是使用很频繁(NIO)

    NIO在JDK1.4引入,使用Native函数直接分配堆外内存,然后通过Java堆中的DirectByteBuffer引用、操作这块内存,避免Java堆和Native堆来回复制数据

    不计入-Jvm的Xmx统计中,但是由于物理内粗的限制,会影响Java堆动态扩展,导致OOM异常


Java对象创建、布局以及定位

包括对象的创建、对象的内存布局以及JVM如何定位对象三部分

创建对象过程

分为6步

  1. 检查new指令参数是否能在常量池中定位到一个类的符号引用,并检查这个类是否已经被加载、解析和初始化过,如果没有,则执行类的加载过程(第7章)
  2. 加载完类后,为对象分配内存,两种方式:
    • 指针碰撞:假设Java堆中内存是绝对规整的,用过的内存在一边,空闲的在另一边,中间以一个指针作为分界点,分配空间只需要将指针向空间内存方向移动与对象大小相等的距离
    • 空闲列表:假设Java堆中的内存是不规整的,已使用和空间的你存交错。虚拟机维护一个列表,记录内存块使用情况。分配时,从列表上找一块足够打的空间给对象实例,并更新列表。
    采用哪种分配方式由堆是否规整决定,而堆的规整又与来及收集器相关。
    • 指针碰撞:CMS(基于Mark-Sweep)
    • 空闲列表:使用Serial,ParNew等带Compact过程的收集器
  3. 将分配到的内存空间初始化为零值
  4. 对对象进行必要的设置,如对象是哪个类的实例,GC分代年龄等信息,存放在对象的对象头中。
  5. 执行对象的 init 方法.

    从虚拟机角度,对象已经产生。但是,从Java程序角度,对象创建才开始。在执行new指令后,执行队形的init方法,将对象按照程序员的意愿初始化。

    完成对象初始化工作

对象的内存布局

在HotSpot中,对象在内存中的布局分为三部分

  1. 对象头包括两部分信息
    • 对象自身运行时数据,如哈希码、GC分代年龄、锁状态等等
    • 类型指针,虚拟机通过这个指针确定对象是哪个类的实例
  2. 实例数据
    存放对象真正存储的有效信息
  3. 对齐填充
    HotSpot VM要求对象起始地址必须是8字节整数倍,这部分占位,补齐8字节

对象的访问定位

主流的两种方式:使用句柄和直接指针

  1. 句柄
    • 句柄包含到实例数据指针和到类型数据的指针。
    • 好处是引用中存储的是稳定的句柄地址,在对象被移动时,只改变句柄中的地址,引用不需要变
  2. 直接指针
    好处是节省了一次指针定位的开销,访问速度更快。

各区域OOM分析

  1. Java堆溢出
    使用内存分析工具,重点确认
    • 是否出现了内存泄漏
    • 确实是内存溢出
  2. 方法区和运行时常量区
    • 加载的类太多?
    • 代码中向常量区中加入太多常量?(如String.intern等方法活着动态生成大量class)
  3. 本机直接内存溢出
    最明显的特征:Heap Dump文件不会看到明显异常
    OOM后发现Dump文件很小,和程序中又使用了NIO,有可能是这部分原因。

书籍 :《深入理解Java虚拟机》第二章 周志明 著