当虚拟机遇到一条new指令时,首先会先检查这个指令的参数是否能在常量池
中定位到一个类的符号引用
,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,必须先执行相应的类加载过程
。
在类检查通过之后,虚拟机会给新生对象分配内存
。对象所需要的内存大小在类加载完成之后就可以确定,为对象分配空间的任务等同于把一块确定的内存从JVM堆
中划分出来。
指针碰撞
(Bump the Pointer): 默认使用; 如果JVM堆中的内存是绝对规整的,所有用过的内存在一边,空闲的内存放在另外一边,中间放一个指针作为分界点的指示器,那所分配内存就是将指针向空闲空间那边挪动一段与对象大小相等的距离空闲列表
(Free List):JVM堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,此时就无法使用指针碰撞了,JVM就必须维护一个空闲内存的列表,记录堆中哪些位置是可用的,在分配内存时,在列表中分配一块足够大的空间给对象实例,并且更新列表上的记录。无规则排列
的,并且未使用的内存空间的大小是不一致的,当一个对象实例需要存储的时候,就需要先去空闲列表中找一个和实例大小相匹配的内存空间,并且还需要更新空闲列表。指针碰撞
:当给对象A分配内存时,指针位置还未及时修改,此时对象B也使用原来的指针来分配内存空间,俗称抢内存。空闲列表
:当给对象A分配内存时,在空闲列表寻找合适对象A的内存空间,如果此时找到一块内存位置,还未及时存入对象A,空闲列表也未做更新,对象B也通过空闲列表找到同一块内存位置,此时就会出现两个对象争抢同一块内存空间的现象。CAS
(Compare And Swap): 比较与交换
,是实现多线程同步的原子指令,将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容更新为给定值。JVM虚拟机采用CAS并且配上失败重试的方式保证更新操作的原子性来对分配内存空间的动作进行同步处理。TLAB
(Thread Local Allocation Buffer):线程本地分配缓存区
,把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存 。内存分配完成后,JVM虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)(如果使用TLAB,此步骤也可以提前至TLAB分配时进行),保证了对象的实例字段在Java代码中可以不赋初始值就可以直接使用,程序能访问到这些字段类型所对应的零值。
★ 初始化零值之后,虚拟机需要对对象进行的必要的设置,例如:这个对象是哪个类的实例,如何才能找到类的元数据信息
,对象的哈希码值
,对象的GC分代年龄
等信息,这些信息存放在对象的对象头Object Heade
r中。
★ 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头
(Header)、实例数据
(Instance Data)和对齐填充
(Padding)。
★ HotSpot虚拟机的对象头包括 两部分信息
第一部分用于存储对象自身的运行时数据,如哈希码
(HashCode)、GC分代年龄
、锁状态标志
、线程持有的锁
、偏向线程ID
、偏向时间戳
等。
另外一部分是类型指针
,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
即对象按照程序的编码进行初始化,也就是属性赋值
(此处赋值,并非赋零值,而是真实的程序编码赋予的值)和执行构造方法
。
Mark word
是一种用于对象头部的标记
,它记录了对象的元数据信息和运行时状态。Klass pointe
: 是指向对象类元数据的指针
,在64位JVM中,klass pointer占据了4字节的空间。模拟: 对象大小和指针压缩(代码如下)
<!-- 可以明细jvm中的对象大小 -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9<