HotSpot虚拟机创建对象过程
1.对象创建过程
加载,验证,准备,解析,初始化,使用,卸载。解析类文件的操作,可以在初始化之前(静态绑定),也可以在初始化之后(动态绑定)
- 首先,会去检查方法区中是否有这个类的符号引用,并且检查这个符号引用代表的类,是否被加载、解析和初始化过
- 如果没有,经过加载和验证以后,会从java堆中分配一块内存(在eden空间),对象所需要的内存,在加载完成以后就完全确定(为什么?结合内存各个区域进行分析)
分配内存有两种方式- 指针碰撞:这种方式,只能用于规整的内存,一边放着用过的内存,一边放着没用过的内存,中间有一个指针作为分界指示器,需要多少内存,指针就往没有用过的内存移动多少。Serial、ParNew带压缩整理功能的垃圾收集器的分配算法,用的是指针碰撞。
- 空闲列表:使用过的内存和空闲内存交错,就不能使用指针碰撞,这个时候虚拟机中,必须维护这一个列表,用于记录可用内存块,在需要分配内存的时候,从中找到一块足够大的内存进行分配。CMS基于清除算法的垃圾收集器,用的就是空闲列表。
分配内存会带来的问题:并发高时,给A对象分配内存的时候,指针位置还没有修改,B又使用了原来的指针,进行了分配。
解决方案: - 对分配内存的动作进行同步处理,采用CAS配上失败重试的机制,保证更新指针的原子性
- 本地线程分配缓冲(Thread Local Allocation Buffer,TLAB):每个线程预先分配一小块内存,这样不同线程分配内存就不会有冲突。这种方法在本地缓冲区用完了以后,分配新的缓冲区,才需要同步锁定。
- 将分配到的空间(不包括对象头)都初始化为O值,如果是用的TLAB,就在本地线程分配缓冲的过程中完成这步。
- 初始化内存值以后,对对象进行必要的设置:哪个类的实例,如何找到类的元数据信息,对象的哈希码,对象的GC分代年龄。在这步完成以后,对象在jvm中已经创建,但是在java的角度,构造函数还没有执行,对象是否还需要其他的资源和状态的信息还没有按照程序员的意图构造好。就是完成了这步,已经到了程序员接触到的生命周期了。
2.对象的访问定位(怎么找到对象的位置)
本来这一节,作者是放在对象内存布局下面的,是一个至下而上的视角。从计算机角度看,是先有内存布局以及内存中的数据的,再有java对这个对象的使用。而作为一个java开发,我觉得至上而下的看,比较好理解。
jvm查找对象的方法 (看不懂这图的,去回顾一下上一讲中的内存分区,以及各个分区存数据)
- 通过句柄访问
2. 直接通过指针访问
3.对象的内存布局(内存中这一块数据的意义,对象在内存中是怎么存在的)
在堆上,对象的内存结分为三块:对象头(Header),实例数据(Instance data)和对其填充
- 对象头:
- 存储对象自身运行的数据,哈希码,GC分代年龄,锁状态标志(标志对象是否被锁),线程持有的锁(这个不理解,对象为什么要考虑线程持有的锁),偏向线程id,偏向时间戳。需要理解,对象头各部分的作用
- 对象指向它的类型元数据的指针,如果对象是数组,还需要保存一个用于存储数组长度的数据,因为类的元数据只能确定类的对象大小,但无法确定数组的大小。
- 实例数据:各字段内容,这部分的顺序会受到虚拟机分配策略参数(-XX:FieldsAllocationStyle)和java源码中字段的定义顺序影响。
- 对齐填充:没啥用