Java是面向对象的静态强类型语言,声明并创建对象的代码很常见,根据某个类声明一个引用变量指向被创建的对象,并使用此引用变量操作该对象 在实例化对象的过程中,JVM中发生了什么化学反应呢?
(1)下面从最简单的
代码进行分析,利用javap -verbose- p命令查看对象创建的字节码
● new : 如果找不到Class对象,则进行类加载
加载成功后,则在堆中分配内存,从Object
开始到本类路径上的所有属性值都要分配内存
分配完毕之后,进行零值初始化
在分配过程中,注意引用是占据存储空间的,它是一个变量,占用4个字节
这个指令完毕后,将指向实例对象的引用变量压入虛拟机栈顶
● dup : 在栈顶复制该引用变量,这时的栈顶有两个指向堆内实例对象的引用变量
如果<init>
方法有参数,还需要把参数压入操作栈中
两个引用变量的目的不同,其中压至底下的引用用于赋值,或者保存到局部变量表,另一个栈顶的引用变量作为句柄调用相关方法
● invokespecial : 调用对象实例方法,通过栈顶的引用变量调用<init>方法
<clinit>
是类初始化时执行的方法(2) 前面所述是从字节码的角度看待对象的创建过程,现在从执行步骤的角度来分析:
● 确认类元信息是否存在
当JVM接收到new指令时,首先在metaspace
内检查需要创建的类元信息是否存在
若不存在,在双亲委派模式下,使用当前类加载器以ClassLoader+包名+类名
为Key进行查找对应的.class文件
ClassNotFoundException
● 分配对象内存 首先计算对象占用空间大小,如果实例成员变量是引用变量,仅分配引用变量空间即可(4个字节),接着在堆中划分一块内存给新对象 在分配内存空间时,需要进行同步操作,比如采用CAS失败重试、区域加锁等方式保证分配操作的原子性
● 设定默认值 成员变量值都需要设定为默认值,即各种不同形式的零值
● 设置对象头 设置新对象的哈希码、GC信息、锁信息对象所属的类元信息等 这个过程的具体设置方式取决于JVM实现
● 执行init方法 初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量
当虚拟机遇到一条含有new的指令时,会进行一系列对象创建的操作
综上所述:JVM究竟采用哪种内存分配方法,取决于它使用了何种GC器 为对象中的成员变量赋上初始值(默认初始化)
至此,整个对象的创建过程就完成了
一个对象从逻辑角度看,由域和方法构成 从物理角度来看,对象是存储在堆中的一串二进制数
对象在内存中存储的布局分三部分
实例数据部分就是程序定义的各种字段的内容,包含父/子类的都会记录下来
HotSpot要求对象的大小必须是8字节的整数倍 由于对象起始地址必须是8字节的整数倍,但实例数据部分的长度是任意的,因此需要对齐补充字段确保整个对象的总长度为8的整数倍
栈上的reference数据存放的是一个地址,那么根据地址类型的不同,对象有不同的访问方式
reference中存放的是对象在句柄池中的地址. 访问对象时,首先需要通过reference找到该对象的句柄,然后根据句柄中对象的地址再访问对象
而HotSpot采用直接指针访问方式
,因为它只需一次寻址操作,节省了一次指针定位的时间开销,对象的访问又十分频繁,从而性能比句柄访问方式快一倍