JVM面试(三)-对象创建、分配内存、对象的内存布局、对象访问定位

2021/9/9 7:03:56

本文主要是介绍JVM面试(三)-对象创建、分配内存、对象的内存布局、对象访问定位,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

对象创建、分配内存、对象的内存布局、对象访问定位

  • 对象创建方式
  • 对象创建过程
  • 为对象分配内存
    • 指针碰撞
    • 空间列表
    • 处理并发安全问题
  • TLAB
  • 对象的内存布局
  • 对象访问定位
    • 指针访问方式
    • 句柄访问方式

对象创建方式

  • 使用new关键字 调用了构造函数
  • 使用Class的newInstance方法 调用了构造函数
  • 使用Constructor类的newInstance方法 调用了构造函数
  • 使用clone方法 没有调用构造函数
  • 使用反序列化 没有调用构造函数

对象创建过程

  1. 虚拟机遇到一条new指令时,先检查 常量池 是否 已经 加载相应的类,如果没有,必须先执行相应的类加载,也就是 检查 这个new指令的参数 是否能 在常量池中 定位到一个类的 符号引用,并且检查 这个符号引用代表的类 是否已经被加载、解析、初始化过
  2. 类加载通过后,接下来分配内存,对象所需的内存大小 在 类加载完成后 便可以确定。为对象分配空间 = 把一块确定大小的内存 从 java堆中划分出来
  3. 内存分配完成后,jvm要将 分配到的内存空间 都初始化为零值。这一步操作保证了 对象的实例字段 在java代码中 可以不赋初始值就能直接访问,程序能访问到 这些字段的 数据类型 所对应的零值
  4. 接下来jvm要对 对象 进行必要的设置,例如,这个对象 是哪个类的实例、如何能找到类的元数据信息、对象的hash码、对象的GC分代年龄等信息。这些信息 存放在 对象的对象头中
  5. 执行new指令之后会接着执行init方法,把对象 按照程序员的意愿进行初始化

为对象分配内存

指针碰撞

如果Java堆的内存是规整,即所有用过的内存放在一边,而空闲的的放在另一边
分配内存时 将 位于中间的指针指示器 向空闲的内存移动一段 与 对象大小相等的距离,这样便完成分配内存工作

空间列表

如果Java堆的内存不是规整的,则需要 由虚拟机 维护一个列表 来记录 那些内存是可用的,这样在分配的时候 可以从列表中 查询到 足够大的内存分配给对象,并在分配后更新列表记录

选择哪种分配方式是由 Java 堆是否规整来决定的,而 Java 堆是否规整 又由 所采用的 垃圾收集器 是否 带有压缩整理功能决定

处理并发安全问题

对象的创建在虚拟机中是一个非常频繁的行为,哪怕只是修改一个指针所指向的位置,在并发情况下也是不安全的,有可能出现正在给对象A分配内存,指针还没来及修改,对象B又同时使用了原来的指针来分配内存的情况
两种解决方案

  • 一种是对分配动作做同步处理-CAS,采用 CAS + 失败重试 来保障 更新操作的原子性
  • 一种是把分配动作按照线程 划分到不同的空间之中执行,即每个线程 在java队中 都预先分配一小块内存,称为 本地线程分配缓冲(Thread Local Allocation Buffer TLAB)。哪个线程需要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定

TLAB

TLAB内存空间位于Eden区。默认TLAB大小为占用Eden Space的1%

  • -XX:+UseTLAB,默认是开启的
  • -XX:+TLABSize,自定义调整TLAB大小
  • -XX:+PrintTLAB,打印TLAB信息
  • -XX:TLABRefillWasteFraction,设置维护 进入TLAB空间 单个对象大小,比例值,默认1/64,对象大于该值会去堆创建
  • -XX:TLABWasteTargetPercent,设置TLAB空间所占用Eden空间的百分比大小
  • -XX:-ResizeTLAB,自动调整TLABRefillWasteFraction阈值

对象的内存布局

对象 在内存中的 存储布局分为三块区域:

  • 对象头header
  • 实例数据instance data,对象真正存储的有效信息,这部分的存储顺序 会受到 虚拟机分配策略参数 和 字段在java中定义顺序 的影响。HotSpot分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、oops。父类中定义的变量会出现在子类之前
  • 对齐填充 padding,不是必然存在的,仅仅起占位符的作用,是因为jvm对 对象的大小 必须是8字节的倍数

对象头包括两部分信息:

  • 存储 对象自身的运行时 的数据,hashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,被称为mard word(这里有可能会被引申到锁的相关问题)
  • 类型指针,即 对象 指向他(自己)的 类(的)元数据 的指针,jvm通过这个指针 来确定 这个对象 是哪个类的实例

在这里插入图片描述

如果对象是一个数组,header中还必须有一块用于记录 数组长度的 数据。因为jvm无法通过数组的 元数据中 确定数组的大小

对象访问定位

Java程序需要通过 JVM 栈上的引用 访问 堆中的具体对象
对象的访问方式取决于 JVM 虚拟机的实现
目前主流的访问方式有 句柄 和 直接指针 两种方式

句柄访问方式好处:reference中存储的是 句柄地址,在对象 被移动时,只改变 句柄中的 实例数据指针, reference本身不需要修改
指针访问方式好处:速度快,节省了一次指针定位的时间开销

指针访问方式

reference中存储的直接就是 对象地址
在这里插入图片描述

句柄访问方式

java堆中 会划分出 一块内存 作为句柄池,reference中存储的就是 对象的句柄(的)地址
句柄中 包含了 对象实例数据 与 对象类型数据 各自的具体地址信息
在这里插入图片描述



这篇关于JVM面试(三)-对象创建、分配内存、对象的内存布局、对象访问定位的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程