0
点赞
收藏
分享

微信扫一扫

Hotspot虚拟机对象探秘

酷子腿长一米八 2022-03-30 阅读 74

Hotspot虚拟机对象探秘

对象的创建

java 中提供的几种对象创建方式:

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

对象的创建主要流程

虚拟机遇到一条new指令时,先检查常量池是否已经加载相应的类,如果没有必须先执行相应的类加载,类加载通过后,接下来分配内存,若java堆中的内存时绝对规整的,使用“指针碰撞”方式分配内存;如果四不规整的,就从空闲列表中分配,叫做“空闲列表”方式。划分内存时还需要考虑并发问题,也有两种方式:CAS同步处理或则本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。然后内存空间初始化操作。接着时做一些必要的对象设置(元信息,哈希码,。。。),最后执行方法。
在这里插入图片描述

为对象分配内存

类加载完成后,接着会在Java堆中划分一块内存分配给对象,内存分配根据Java堆是否完整有两种方式:

  • 指针碰撞:如果Java堆中的内存是规整的,即所有用过的内存放一边,而空闲的放在另一边,分配内存时将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离。这样就完成了内存分配工作
  • 空闲列表:如果java堆中的内存时不规整的,则需要由虚拟机维护一个列表来记录哪些内存时可以使用的,这样分配的时候可以从列表中查询到足够大的内存来分配给对象,并在分配后更新列表记录。

选择哪种分配方式是由java堆是否规整来决定的。而java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
在这里插入图片描述

处理并发安全问题

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

  • 对分配内存空间的动作进行同步处理(采用CAS+失败重试来保障更新操作的原子性)
  • 把内存分配的动作按照线程划分在不同的空间中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲(Thead Local Allocation Buffer,TLAB)。哪个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁。通过-XX:+/-UserTLAB参数来设定虚拟机中是否使用TLAB。
    在这里插入图片描述

对象的访问定位

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

  • 指针:指向对象,代表一个对象在内存中的起始位置
  • 句柄:可以理解为指向指正的指针,维护这对象的指针,句柄不直接访问对象,而是指向对象的指针(句柄不发生变化,指向固定的内存地址),在由对象的指针指向对象的内存地址

句柄访问

java堆中划分出一块内存来作为句柄池,引用中存储对象的句柄地址,而句柄中包含了对象实例数据与对象类型数据各自的具体地址信息,具体构造如下图所示:
在这里插入图片描述

优势:引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而引用本身不需要修改。

直接指针

如果使用直接指针访问,引用中存储的直接就是对象地址,那么java堆对象内部的布局就必须考虑如何放置访问类型的相关信息
在这里插入图片描述
优势:速度更快,节省了一次指针定位的时间开销,由于对象访问在java中年非常频繁,因此这类开销积少成多后也是非常可观的执行成本。Hotspot中采用的就是这种方式。

举报

相关推荐

0 条评论