请简单阐述一下对象的创建过程?
先看一张图
main方法中创建了两个对象执行过程在右边字节码中展示完全一致new、dup、invokespecial、astore四个步骤
1、new,虚拟机指令为对象分配内存并在栈顶压入了指向这段内存的地址供后续操作来调用
2、dup,其实就是一个复制操作,其作用是把栈顶的内容复制一份再压入栈。jvm为什么要这么做呢
这完全是jvm自己编译优化的做法,再后续操作之前虚拟机自己会调用一次。我们都知道对象都有一个this的关键字指向对象本身,this是什么时候赋值的呢,就是这个时候
至于另一个引用当然是赋值给方法中的变量了
3、invokespecial,该过程是对实例对象进行初始化,第一步分配内存后对象内的实例变量都是初始值,在该步骤才会初始化对象内的实例变量
4、astore,方法内的变量指向内存中的对象
如何定位一个对象
1、直接定位也就是指针定位(hotspot使用方式),直接定位到实例对象内存地址。这种方式的好处是速度快(对比句柄访问,减少一次指针开销);缺点是GC的时候需要改变指针的指向
2、句柄定位,在堆中划分出一块内存区域作为句柄池,变量都指向句柄池内的地址,再句柄池内指向实例对象以及Class。好处是在GC的时候只需要改变句柄池中的地址。缺点就是查找慢,毕竟多了一次指针开销
对象在内存中的布局
总的来说对象在内存中布局总共分为三部分对象头(mark World、klass pointer)、实例数据、对齐,见下图标记部分
1、mark world:锁信息、hashCode、gc信息
通过上图对比,synchronized加锁之后,我们可以明显看到markword内信息发生变化
gc信息包含gc年龄、颜色标记等信息
2、klass pointer:我这里关闭了指针压缩所以看到的klass pointer 是8个字节,jdk8默认是开启指针压缩的在内存小于32G的时候klass pointer是4个字节。这部分内容主要是指向当前对象的类型也就是class对象
3、实例数据:这部分主要看类中到底有哪些成员变量,如上图有成员变量int,所以占4个字节
4、对齐填充:这部分是可有可无的,对象的大小是8的整数倍,如果无法被8整除,就需要补充对齐,如上述对象需要补充4字节对齐
对象是在内存中是如何分配的
引用一张大神的图:
简单来说对象分配的流程 (当然其中有很多细节,对象分配也与使用的gc有关)
1、判断对象是否可以在栈上分配(逃逸分析、标量替换)
2、判断对象的大小如果过大直接分配到老年代(-XX:PretenureSizeThreshold)
3、否则分配到eden区,经过gc后,存活的对象移动到 from Survivor区;再次经过gc存活的对象移动到To survivor区 同时两个survivor互换身份(eden、survivor比例参数 -XX:SurvivorRatio)
4、gc年龄达到阈值,对象移动到老年代(-XX:MaxTenuringThreshold指定移动到老年代gc年龄)