对象创建、分配内存、对象的内存布局、对象访问定位
对象创建方式
- 使用new关键字 调用了构造函数
- 使用Class的newInstance方法 调用了构造函数
- 使用Constructor类的newInstance方法 调用了构造函数
- 使用clone方法 没有调用构造函数
- 使用反序列化 没有调用构造函数
对象创建过程
- 虚拟机遇到一条new指令时,先检查 常量池 是否 已经 加载相应的类,如果没有,必须先执行相应的类加载,也就是 检查 这个new指令的参数 是否能 在常量池中 定位到一个类的 符号引用,并且检查 这个符号引用代表的类 是否已经被加载、解析、初始化过
- 类加载通过后,接下来分配内存,对象所需的内存大小 在 类加载完成后 便可以确定。为对象分配空间 = 把一块确定大小的内存 从 java堆中划分出来
- 内存分配完成后,jvm要将 分配到的内存空间 都初始化为零值。这一步操作保证了 对象的实例字段 在java代码中 可以不赋初始值就能直接访问,程序能访问到 这些字段的 数据类型 所对应的零值
- 接下来jvm要对 对象 进行必要的设置,例如,这个对象 是哪个类的实例、如何能找到类的元数据信息、对象的hash码、对象的GC分代年龄等信息。这些信息 存放在 对象的对象头中
- 执行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中存储的就是 对象的句柄(的)地址
句柄中 包含了 对象实例数据 与 对象类型数据 各自的具体地址信息
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。