jvm中对象的创建过程

1. 类加载检查

虚拟机遇到一条 new 指令时,首先检查这个指令的参数能否在常量池中找到一个类符号引用,并且检查这个符号引用代表的类是否已经被加载,解析,初始化过。如果没有必须先执行类的加载初始化过程。

2. 分配内存

在类加载检查通过后,接着就可以为新生对象划分内存了,对象占用内存的大小在类加载后就可以完全确定。为对象分配内存空间就相当于把一块确定大小的内存从java堆中划分出来

如何划分内存呢

内存是如何划分的呢?高并发的场景下如何保证同一块空间不会分给两个对象的呢?

指针碰撞 Bump the pointer (默认)

如果虚拟机堆中内存是绝对规整的,用过和没用过的各占一块完整的内存,中间放着一个指针作为分界点的指示器,在进行内存分配时,只需把指针向空闲区域移动一段距离,以放下新对象。

空闲列表 Free List

如果虚拟机堆中的内存不是规整的,用过的和没有用过的互相交错,就没有办法使用指针碰撞的方法;了。虚拟机必须维护一个列表,来记录队中有哪些区域是空闲的。在分配内存的时候找到一块足够大的空间分配给对象,并更新列表记录

解决并发

CAS (compare and swap)

  • 虚拟机采用CAS配上失败重试的方式保证更新操作的原子性来对分配内存空间的动作进行同步处理。

  • CAS 通常指的是这样一种原子操作:针对一个变量,首先比较它的内存值与某个期望值是否相同,如果相同,就给它赋一个新值。CAS 指令作为一种硬件原语,有着天然的原子性,这也正是 CAS 的价值所在。

本地线程分配缓冲(Thread Local Allocation Buffer TLAB)

每个线程预先在jvm堆中分配一块内存空间,线程声明周期内的对象分配都在这实现分配的空间中进行

-XX: +UseTLAB 设置虚拟机使用TALAB -XX:-UseTLAB 不使用

-XX:TLABSize 指定TALB 的大小

3. 初始化

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值,(不包括对象头),这一步操作保证了对象的实例字段在java代码中可以不赋初始值就能直接使用,如果是TLAB 方式,这一步骤将提前到TLAB分配时进行

4. 设置对象头

初始零值之后,要虚拟机要对对象进行一些必要的设置,比如,这个对象是哪个类的实例,如何才能找到类的一些元信息,对象的哈希码,对象的GC分代年龄信息,这些信息存放在对象头 Object-Header 中。

在HotSpot虚拟机中,对象在内存中分布可以又3各部分祖成,对象头,实例数据,对齐填充。

对象头

HotSpot 虚拟机的对象头包含两个部分

4.1 自身的运行数据

  • 哈希码
  • GC分代年龄
  • 锁状态标识
  • 线程持有锁
  • 偏向锁ID
  • 偏向时间戳等

实例数据

存放对象的实际数据

对齐填充

不是必然存在,也没有特别的含义,只是起着占位符的作用,那么为什么会有这一部分的数据呢?

这就需要了解一些计算机组成原理的知识了。

CPU 位数

我们经常使用的CPU 有32 位和64 位

32位架构的CPU数据总线宽度是32位,每次可以传输32位数据,可以计算4个字节。

64位架构的CPU数据总线宽度是64位,每次可以传输64位数据,可以计算8个字节。

CPU 寻址

CPU 在工作时,从内存中的某个地址取数据,然后进行计算。数据最小的保存单位时 位 (bit), 为了减少CPU与内存通信的次数,规定CPU一次从内存中取8位数据,也就是1字节。内存中每一位(bit) 都有一个唯一的地址。

对于32位的CPU,在向内存取数据时,所能描述的最大地址是第 2^32 字节(CPU一次最少取1个字节,不是1位),2^32字节 约等于 4G, 内存再大,CPU 也无法访问到,所以32位的CPU 最大支持约4G的内存了。 同理,64 位的CPU 支持 2^64 字节内存,等于 17179869184 Tb

Java对象最小占用空间

由于64 位的CPU一次能处理 64 位指令,也就是8个字节的数据,JVM 也遵循这个规则,让对象的内存占用是8字节的的整数倍,CPU 处理更高效。那么如何保证 java 对象占用的空间永远是8字节的整数倍呢? 答案就是内存对齐,不足8字节的整数倍,末尾补充空白占用符。

Java对象的指针压缩

上述的内存对齐,相当于把对象的占用的空间扩大了,与之对应的,JVM 还有内存压缩机制:

  • jdk1.6 update14开始,在64bit操作系统中,JVM支持指针压缩
  • 启用指针压缩:–XX:+UseCompressedOops(默认开启),禁止指针压缩:–XX:-UseCompressedOops
指针压缩的好处
  1. 在64位平台的HotSpot中使用32位指针,内存使用会多出1.5倍左右,使用较大指针在主内存和缓存之间移动数据,占用较大宽带,同时GC也会承受较大压力
  2. 为了减少64位平台下内存的消耗,启用指针压缩功能
  3. 在jvm中,32位地址最大支持4G内存(2的32次方),可以通过对对象指针的压缩编码、解码方式进行优化,使得jvm只用32位地址就可以支持更大的内存配置(小于等于32G)
指针压缩的注意点
  1. 堆内存小于4G时,不需要启用指针压缩,jvm会直接去除高32位地址,即使用低虚拟地址空间
  2. 堆内存大于32G时,压缩指针会失效,会强制使用64位(即8字节)来对java对象寻址,这就会出现1的问题,所以堆内存不要大于32G为好

4.2 类型指针

对象指向他类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例

5. 执行<init>方法

执行<init> 方法,就是按照程序员的意愿进行初始化,对应到语言层面上就是为属性赋值,和执行构造方法