Java 是面向对象的静态强类型语言,声明并创建对象的代码很常见,根据某个类声明一个引用变量指向被创建的对象,并使用此引用变量操作该对象。在实例化对象的过程中,JVM 中发生了什么化学反应呢?
1.下面从最简单的 Object ref= new Object();代码进行分析,利用javap -verbose -p 命令查看对象创建的字节码如下:
stack=2, locals=2, args_size=1
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: return
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
8 1 1 ref Ljava/lang/Object;
- NEW: 如果找不到Class对象,则进行类加载。加载成功后,则在堆中分配内存,从 Object 开始到本类路径上的所有属性值都要分配内存。分配完毕之后,进行零值初始化。在分配过程中,注意引用是占据存储空间的,它是一个变量,占用4个字节。这个指令完毕后,将指向实例对象的引用变量压入虚拟机栈顶。
- DUP:在栈顶复制该引用变量,这时的栈顶有两个指向堆内实例对象的引用变量。如果 <init> 方法有参数,还需要把参数压入操作栈中。两个引用变量的目的不同,其中压至底下的引用用于赋值,或者保存到局部变量表,另个栈顶的引用变量作为句柄调用相关方法。
- INVOKESPECIAL: 调用对象实例方法,通过栈顶的引用变量调用<init>方法。<clinit> 是类初始化时执行的方法,而 <init> 是对象初始化时执行的方法。
2.前面所述是从字节码的角度看待对象的创建过程,现在从执行步骤的角来分析。
- 确认类元信息是否存在。当JVM 接收到 new 指令时,首先在 metaspace内查需要创建的类元信息是否存在。若不存在,那么在双亲委派模式下,使用当前类加载器以ClassLoader+ 包名+类名为 Key进行查找对应的class文件如果没有找到文件,则抛出 ClassNotFoundException 异常;如果找到,则讲行类加载,并生成对应的Class 类对象。
- 分配对象内存。首先计算对象占用空间大小,如果实例成员变量是引用变量,仅分配引用变量空间即可,即 4 个字节大小,接着在堆中划分一块内有给新对象。在分配内存空间时,需要进行同步操作,比如采用CAS( Compar And Swap)失败重试、区域加锁等方式保证分配操作的原子性。
- 设定默认值。成员变量值都需要设定为默认值,即各种不同形式的零值。
- 设置对象头。设置新对象的哈希码、GC信息、锁信息、对象所属的类元信息等这个过程的具体设置方式取决于JVM实现。
- 执行 init 方法。初始化成员变量,执行实例化代码块,调用类的构造方法并把堆内对象的首地址赋值给引用变量。
网友评论