内存模型

Xmind Zen 挺好用的

学习了《深入理解Java虚拟机》–周志明之后的一点点个人总结,如有错误还请指正。

jvmMemory
运行时内存
文字版如下

运行时内存

线程私有

虚拟机栈

  • 描述

    • java方法执行的内存模型

      • 存储局部变量表、操作数栈、动态链接、方法出口等信息
    • 方法开始执行时,创建栈帧

      • 从方法执行到执行完成对应(一个栈帧)入栈出栈的过程
  • 功能

    • 局部变量表

      • 存放该方法调用者所传入的参数,及在该方法的方法体中创建的局部变量。
    • 对象引用(reference类型)

      • 可能是指向对象起始地址的引用指针
      • 也可能是指向一个代表对象的句柄
    • returnAddress类型

      • 指向一条字节码指令的地址
  • 多线程

    • 生命周期与线程一致
  • 抛出异常

    • 线程请求深度大于虚拟机栈允许最大深度时

      • StackOverflowError
    • 虚拟机栈可以动态扩展,无法申请到足够内存

      • OutOfMemoryError

本地方法栈

  • 描述

    • 与虚拟机栈十分相似

      • 只不过针对Native方法服务
  • 功能

  • 多线程

    • 生命周期与线程一致
  • 抛出异常

    • 同虚拟机栈异常

程序计数器

  • 描述

    • 当前线程所执行字节码的行号指示器
  • 功能

    • 字节码解释器改变计数器的值
    • 如果执行的是native方法,则计数器值为空
  • 多线程

    • 每个线程的计数器互不影响,相互独立

jvm多线程的理解

  • jvm通过轮流分配处理器执行时间,实现多线程

共享区域

java堆

  • 描述

    • 虚拟机启动时创建
  • 功能

    • 存放对象的实例

      • jit编译器与逃逸分析技术的发展,栈上分配、标量替换使这一条不那么绝对
    • 存放数组

    • 为了更好的分配和回收会进行细分、功能不变

      • 新生代和老年代
      • Eden空间、From Survivor空间、To Survivor空间
      • 线程私有的分配缓冲区

        • TLAB
  • GC

    • 内存回收的主要区域
  • 抛出异常

    • 堆中没有内存可以完成实力分配并且没法扩展时

      • OutOfMemoryError
  • 参数

    • -Xmx
    • -Xms

方法区

  • 主区域

    • 描述

      • 堆的逻辑区域
    • 功能

      • 存储已被虚拟机加载的类信息

        • 类的版本、字段、方法、接口等描述信息
        • 常量池
      • 常量

      • 静态变量
      • 即时编辑器编译后的代码
    • 特点

      • 不需要连续的内存空间
      • 可固定可扩展
      • 可以不实现垃圾回收(垃圾回收在此区相对较少)
    • 参数

      • 有些虚拟机把分代收集扩展到方法区称为永久代
      • 永久代大小

        • -XX:MaxPermSize
  • 运行时常量池

    • 描述

      • 用于存放编译期生成的各种字面变量和符号引用
    • 功能

      • 还可以保存翻译出来的直接引用
    • 特点

      • 相对于Class文件常量池具备动态性

        • 运行期间也可以讲新的常量放入池中

          • String.intern()
  • 抛出异常

    • 方法区无法满足内存分配需求时

      • OutOfMemoryError

直接内存

  • 描述

    • NIO类可以操作Native函数库直接分配堆外内存
    • 通过存储在堆中的DirectByteBuffer对象作为这块内存引用操作
  • 抛出异常

    • OutOfMemoryError
  • 参数

    • 主动设置

      • -XX:maxDirectMemorySize
    • 默认

      • -Xms

对象

对象的创建

  • 类加载

    • 先通过New指令的参数去常量池中定位到一个类的符号引用

      • 编译时并不知道一个类的直接内存地址,只能使用(符号)来标识类的地址
    • 如果没有,执行相应的类加载过程

  • 分配内存空间

    • 类加载完成后所需的大小可以完全确定
    • 将所需的内存区域从Java堆中划分出来
    • 内存分配方式

      • 规整的内存(已用和未用有一个区分界限)
      • 零散的区域

        • 需要一个空闲列表来统计空闲区域
    • 内存分配时并发性问题

      • 描述

        • 正在给A对象分配,指针还没来得及移动,要开始分配B对象,使用了A对象内存区域
      • 解决方案

        • 动作原子性控制

          • CAS配上失败重试
        • 作用域控制

          • 为每个线程在堆中分配TLAB,每个线程在自己的区域中划分内存
          • 如果原来的缓冲区域用完了,需要新的缓冲区来划分内存,则需要同步锁来保证完整性
          • 启用TLAB

            • -XX:+/-UseTLAB
  • 翻台

    • 将分配的内存区域置零

      • 不包括对象头
      • 如果使用TLAB,这一步在TLAB分配时完成
  • 设置

    • 根据对象头

      • 对象是那个类的实例
      • 如何才能找到类的元数据信息
      • 对象的哈希码
      • 对象的GC分代年龄等
  • 至此一个对象创建完成,但是对于Java程序来讲对象的创建才刚刚开始,接下来执行init方法

对象的内存布局

  • 对象头包含两部分信息

    • 运行时数据、信息

      • 位数固定

        • 如果储存的信息太多时会复用位数空间
    • 类型指针

      • 即指向它类元数据的指针
      • 虚拟机通过这个指针来确定对象是哪个类的实例
  • 真正储存的有效信息

    • 各类型字段(包括父类)

      • 存储顺序

        • 虚拟机分配策略参数

          • 相同宽度的字段总是被分配到一起
          • 父类定义的变量会在子类之前
        • 字段在Java源码中的定义顺序

  • 对齐填充

    • 保证地址空间时8字节的整数倍

对象的访问定位

  • 通过栈上的reference数据来操作对象

    • 指向对象的引用
  • 访问方式

    • 句柄

      • Java堆中划分出一块句柄池
      • reference存储句柄地址
      • 句柄包含对象实例数据与类型数据各自的具体信息
    • 直接指针

      • Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息
    • 优缺点

      • 直接指针访问速度快,对象移动时需要改变栈中reference
      • 句柄每次访问查一遍句柄池,但是对象移动(垃圾收集时)不需要修改栈中reference