之前的文章
上篇大体说了jvm运行时候的数据区域,这篇我们来说说对象创建的过程。注意哈,这篇文章应该是对你写高性能的代码是有帮助的。
对象真的是振奋单身狗们心灵的一个词,在面向对象编程里面就更爽了,想new多少就new多少,想new什么样的就new什么样的。
好了,说正经的,对象创建是我们编程中做的非常频繁的一件事情,那么对象创建都经历了什么过程呢?我们这篇里面仅仅说遇到new指令时候所进行的操作。
我们以下面这段代码为例
public class TestNew {
public static void main(String[] args) {
new MyEntity(1,"a");
}
}
MyEntity.java
@Setter
@Getter
public class MyEntity {
private Integer id;
private String name;
public MyEntity(Integer id) {
this.id = id;
}
public MyEntity(Integer id, String name) {
this.id = id;
this.name = name;
}
}
我们不多BB,直接javap -v TestNew.class,来看main方法字节码(别害怕,我一句一句解释给你)
如下是main方法对应字节码
为了方便讲解,我把它粘了下来:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=1, args_size=1
0: new #2
3: dup
4: iconst_1
5: invokestatic #3
8: ldc #4
10: invokespecial #5
13: pop
14: return
LineNumberTable:
line 8: 0
line 9: 14
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 args [Ljava/lang/String;
MethodParameters:
Name Flags
args
我们一点一点的来说,第5句这里其实做了很多事情,大概如下:
-
首先检查这个new指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、验证、准备、解析、和初始化过。如果没有那就表明该类还没有被虚拟机加载无法创建,需要先执行类的加载过程(这里这个类其实就是MyEntity)。
-
类加载检查之后,jvm在java堆中按照 “指针碰撞” 或者 “空闲列表” 的方式为新生的对象分配内存,分配内存的大小在类加载完成后便可完全确定(由于我们使用的垃圾回收器是CMS,所以是按照空闲列表的方式来的)。
-
内存分配完成之后,jvm需要将分配到的内存空间进行初始化零值(不包含对象头),该步骤保证了对象的实例字段在Java代码中可以不赋初始值就可以直接使用,程序能访问到这些字段的数据类型所对应的零值。(实例变量无需初始化即可被程序使用,区别于局部变量的必须初始化才能被程序使用)
-
接下来对对象进行必要的设置:这个对象是哪个类的实例、如何找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息、这些信息存放于对象的对象头信息中。
到这里new指令做的事情就完了,接下来要填充对象实例了,也就是该给成员属性赋值了,但是成员属性的值也需要一个准备过程,你肯定觉得很奇怪,为什么还需要准备呢?我不是已经在new MyEntity(1,“a”)中把值给了吗?这是因为你给的1其实是int类型,但是id这个成员属性其实是Integer类型,因此需要装箱,因此就有了8这里这个,然后“a”是在字符串常量池的,需要从常量池中把引用加载到栈顶来,也就是9这里的操作,这里都准备好了,才在10调用了MyEntity类的构造方法初始化成员变量。10这里做的事情可以概括如下:
5.开始执行方法进行对象的初始化,按照程序猿的意愿初始化对象,到这里一个成品的对象就算是加载完了
我上面说的12345就是对象创建的完整过程,细节你可以对着字节码的看看,下一篇说对象创建过程的内存分配。
有什么问题不明白也可以在评论区问我,我并非没耐心的人,只是比较忙,你问的问题我看到的话一定会认真给你解释。