首页 > 技术文章 > Java虚拟机(3)-对象的创建,内存布局,定位

guan-li 2019-09-09 21:13 原文


1.对象的创建过程

在虚拟机遇到new指令后,会先去常量池找到该类的符号引用.并判断该符号引用是否已经加载,如果没有则先执行类加载的过程.


类加载后,会执行内存分配的动作.内存分配主要有两种方式:

  • 指针碰撞
    内存规整时使用指针碰撞,每次分配内存后,指针移动.

  • 空闲列表
    内存不规整,使用空闲列表.虚拟机需要维护一张内存位置使用情况的列表,分配时找到足够大的内存即可.

内存是否规整和垃圾回收器是否具有压缩整理功能有关,Serial/ ParNew等回收器有Compact过程,通常使用指针碰撞.

CMS这种使用标记清除算法的回收器,通常使用空闲列表.


另外,内存分配动作是线程不安全的,可能发生正在给对象A分配内存,指针还没来得及修改,对象B又用原来的指针来分配内存,解决此问题的方式有两种:

  • CAS + 失败重试
    CAS(compare and set),指的是CPU的原子性操作,保证内存分配动作是同步的,失败的分配重新来过直到成功.

  • 本地线程分配缓冲TLAB(Thread Local Allocation Buffer)
    每个线程预先在堆中分配一小块内存,TLAB用完时,才需要同步锁定.

内存分配完毕后,虚拟机会执行方法,为对象的各个字段进行赋值.


2.对象的内存布局

对象在内存中的存储布局主要分为3块.

  • 对象头
    存储对象的运行时信息,如哈希吗/GC分代年龄/所状态标志/线程持有的锁/偏向线程ID/偏向时间戳等.对象头数据的长度一般定义为32bit或64bit,既"MARK WORD".

  • 实例数据
    存储对象中的具体数据,如字段的赋值等.

  • 补充对齐
    不一定存在也没有实际意义,仅仅起到占位符作用.


3.对象的定位

对象创建时的引用,会作为一个reference类型数据保存在栈的局部变量表中.对象的访问会通过reference中记录的位置来定位.

  • 句柄访问

采用句柄方式访问时,虚拟机会再堆中开辟一块区域作为句柄池.
reference会执行句柄池中的某个句柄,句柄中记录了该对象的实例数据位置(堆中)和类型数据位置(方法区中).

句柄访问的优点是,reference中存储的是稳定的句柄地址,对象被移动时(如垃圾回收),只改动句柄中记录的实例数据的位置.

缺点是需要两次定位.


  • 指针访问

指针方式访问时,reference直接存储堆中的实例数据地址.

优点是只需一次定位,速度快.
缺点是当对象被移动后,reference中的数据需要被更改.

HotSpot使用的指针方式进行访问,使用句柄方式的场景也比较多见.


推荐阅读