1、对象创建
1、检查该指令(new)是否在常量池中有相关类的符号引用
2、检查是否执行过类加载的过程。
3、类加载完成后,分配内存,分配内存有两种方法
3.1、指针碰撞,前提是Java堆中内存是绝对规整的, 用过的内存在一边,没用过的在另外一边,分配内存的时候就把指针往没用过的那一边挪动和对象大小相同的距离。
3.2、空闲链表,在Java堆内存不规整的时候使用,链表记录空闲内存,分配内存的时候,从链表找到符合内存大小的空间。
4、对象创建是个频繁的过程,需要考虑多线程的情况,为了保证内存分配正常,有两种解决方案:
a、通过CAS+失败重试的方法
b、每个线程预先分配一块内存,为本地线程分配缓冲(TLAB)
5、内存分配完成之后,虚拟机初始化值(举个例子,int num : num会有一个默认值),接下来对对象进行设置,最后调用<init>方法(举个例子, int num = 2; 给num赋值为2),执行完之后,对象就算真正的生成了。
2、对象的内存布局
分为三块区域,对象头、实例数据、对其填充,如下图所示:
对象头
分为两部分,一部分是
1、哈希码、对象分代年龄
2、指向锁的记录的指针
3、指向重量级锁的指针
4、偏向线程ID、偏向时间戳、对象分代年龄
另外一部分为类型指针,需要注意如果对象是数组,对象头应该还需要记录数组长度。
实例数据
程序代码中所定义的类型字段信息
对齐填充
可以不存在,也没啥特别的含义,为了在对象实例数据部分没有对齐时,就需要这个对齐填充去补足了。
3、对象的访问定位
顾名思义,就是如何找到对象在堆上的具体位置的,有两种方法,分别为句柄和直接指针。下面分别介绍下这两个的定义和区别。
句柄访问
Java堆会专门划分一块内存来作为句柄池,reference中存储的也就是对象的句柄地址,而句柄中包含了对象的实例数据与类型数据各自的地址信息,如下图所示:
直接指针访问
跳过了句柄池,直接通过引用变量来访问对象地址,这很直接,直接上图:
区别:
1、使用句柄,当对象被移动的时候,只需要改变句柄中的实例对象指针,而不需要改变reference。
2、使用直接指针,少了句柄池那一步,那肯定快点咯,节省了一次指针定位的时间开销,需要注意的是,
Sun HotSpot ,它是使用直接指针来进行对象访问的。
4、参考
1、https://github.com/doocs/jvm/blob/main/docs/02-hotspot-jvm-object.md
2、《深入理解Java虚拟机》 -周志明