Java 杂谈JVM程序员

JVM实战 - 对象实例化

2019-01-11  本文已影响9人  紫霞等了至尊宝五百年

Java是面向对象的静态强类型语言,声明并创建对象的代码很常见,根据某个类声明一个引用变量指向被创建的对象,并使用此引用变量操作该对象
在实例化对象的过程中,JVM中发生了什么化学反应呢?

(1)下面从最简单的


代码进行分析,利用javap -verbose- p命令查看对象创建的字节码


● new : 如果找不到Class对象,则进行类加载
加载成功后,则在堆中分配内存,从Object开始到本类路径上的所有属性值都要分配内存
分配完毕之后,进行零值初始化
在分配过程中,注意引用是占据存储空间的,它是一个变量,占用4个字节
这个指令完毕后,将指向实例对象的引用变量压入虛拟机栈顶

● dup : 在栈顶复制该引用变量,这时的栈顶有两个指向堆内实例对象的引用变量
如果<init>方法有参数,还需要把参数压入操作栈中
两个引用变量的目的不同,其中压至底下的引用用于赋值,或者保存到局部变量表,另一个栈顶的引用变量作为句柄调用相关方法

● invokespecial : 调用对象实例方法,通过栈顶的引用变量调用<init>方法

(2) 前面所述是从字节码的角度看待对象的创建过程,现在从执行步骤的角度来分析:
● 确认类元信息是否存在
当JVM接收到new指令时,首先在metaspace内检查需要创建的类元信息是否存在
若不存在,在双亲委派模式下,使用当前类加载器以ClassLoader+包名+类名为Key进行查找对应的.class文件

● 分配对象内存
首先计算对象占用空间大小,如果实例成员变量是引用变量,仅分配引用变量空间即可(4个字节),接着在堆中划分一块内存给新对象
在分配内存空间时,需要进行同步操作,比如采用CAS失败重试、区域加锁等方式保证分配操作的原子性

● 设定默认值
成员变量值都需要设定为默认值,即各种不同形式的零值

● 设置对象头
设置新对象的哈希码、GC信息、锁信息对象所属的类元信息等
这个过程的具体设置方式取决于JVM实现

● 执行init方法
初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量

1 对象的创建过程

当虚拟机遇到一条含有new的指令时,会进行一系列对象创建的操作

  1. 检查常量池中是否有要创建的这个对象所属类的符号引用
  1. 检查这个符号引用所代表的类是否已被JVM加载
  1. 根据方法区中该类的信息确定该类所需的内存大小
    一个对象所需的内存大小是在这个对象所属类被定义完就能确定的!
    且一个类所生产的所有对象的内存大小是一样的!
    JVM在一个类被加载进方法区的时候就知道该类生产的每一个对象所需要的内存大小
    从堆中划分一块对应大小的内存空间给新的对象,分配堆中内存有两种方式

综上所述:JVM究竟采用哪种内存分配方法,取决于它使用了何种GC器
为对象中的成员变量赋上初始值(默认初始化)

  1. 设置对象头(Object Header)
  2. 调用对象的构造函数进行初始化

至此,整个对象的创建过程就完成了

2 对象的内存布局

一个对象从逻辑角度看,由域和方法构成
从物理角度来看,对象是存储在堆中的一串二进制数

对象在内存中存储的布局分三部分

2.1 对象头

2.2 实例数据

实例数据部分就是程序定义的各种字段的内容,包含父/子类的都会记录下来

2.3 对齐填充(并非必然存在,无特别含义,仅起占位符作用)

HotSpot要求对象的大小必须是8字节的整数倍
由于对象起始地址必须是8字节的整数倍,但实例数据部分的长度是任意的,因此需要对齐补充字段确保整个对象的总长度为8的整数倍

3 访问对象的过程

栈上的reference数据存放的是一个地址,那么根据地址类型的不同,对象有不同的访问方式

上一篇下一篇

猜你喜欢

热点阅读