在虚拟机遇到new指令后,会先去常量池找到该类的符号引用.并判断该符号引用是否已经加载,如果没有则先执行类加载的过程.
类加载后,会执行内存分配的动作.内存分配主要有两种方式:
指针碰撞
内存规整时使用指针碰撞,每次分配内存后,指针移动.
空闲列表
内存不规整,使用空闲列表.虚拟机需要维护一张内存位置使用情况的列表,分配时找到足够大的内存即可.
内存是否规整和垃圾回收器是否具有压缩整理功能有关,Serial/ ParNew等回收器有Compact过程,通常使用指针碰撞.
CMS这种使用标记清除算法的回收器,通常使用空闲列表.
另外,内存分配动作是线程不安全的,可能发生正在给对象A分配内存,指针还没来得及修改,对象B又用原来的指针来分配内存,解决此问题的方式有两种:
CAS + 失败重试
CAS(compare and set),指的是CPU的原子性操作,保证内存分配动作是同步的,失败的分配重新来过直到成功.
本地线程分配缓冲TLAB(Thread Local Allocation Buffer)
每个线程预先在堆中分配一小块内存,TLAB用完时,才需要同步锁定.
内存分配完毕后,虚拟机会执行
对象在内存中的存储布局主要分为3块.
对象头
存储对象的运行时信息,如哈希吗/GC分代年龄/所状态标志/线程持有的锁/偏向线程ID/偏向时间戳等.对象头数据的长度一般定义为32bit或64bit,既"MARK WORD".
实例数据
存储对象中的具体数据,如字段的赋值等.
补充对齐
不一定存在也没有实际意义,仅仅起到占位符作用.
对象创建时的引用,会作为一个reference类型数据保存在栈的局部变量表中.对象的访问会通过reference中记录的位置来定位.
采用句柄方式访问时,虚拟机会再堆中开辟一块区域作为句柄池.
reference会执行句柄池中的某个句柄,句柄中记录了该对象的实例数据位置(堆中)和类型数据位置(方法区中).
句柄访问的优点是,reference中存储的是稳定的句柄地址,对象被移动时(如垃圾回收),只改动句柄中记录的实例数据的位置.
缺点是需要两次定位.
指针方式访问时,reference直接存储堆中的实例数据地址.
优点是只需一次定位,速度快.
缺点是当对象被移动后,reference中的数据需要被更改.
HotSpot使用的指针方式进行访问,使用句柄方式的场景也比较多见.
原文:https://www.cnblogs.com/guan-li/p/11494302.html