一个java对象创建的全过程(学习笔记)

422 阅读3分钟

当java虚拟机遇到一条new指令时就开始了对象创建的过程如下:

1,首先检查这个new指令后面跟的参数是否可以在常量池中找到一个类的符号引用。

2,检查这个符号引用代表的类是否已经被加载,解析和初始化过。如果没有则必须先进行加载,解析和初始化的过程。

3,对象所需内存的大小在类加载检查通过后便可以确定了,在堆中为对象分配相应大小的内存分配内存可以按“指针碰撞”或者“空闲列表”的方式进行分配,具体选择那种方式则由java堆是否规整决定,而java堆是否规整则由不同的垃圾回收策略决定,不同的回收策略则由具体的虚拟机实现决定。

4,堆内存线程共享,用来存放对象实例,那么在为对象分配内存的过程中必然会出现并发问题,虚拟机是一般解决内存分配并发问题的策略有两个,一个是采用CAS+失败重试方式保证,另一种就是每个线程在java堆中预先分配一小块内存称为本地线程分配缓冲(TLAB),给对象分配内存的时候可以直接在TLAB中分配,只有TLAB用完了,分配新的缓存区的时候才需要同步锁,这样既可以保证线程安全的为对象分配内存还能提升效率。

5,为对象的实例属性赋零值,这个零值的意思是不同数据类型规定的初始值。

6,设置对象头(Object_header)信息,对象头信息包括两部分,一类是对象自身运行时数据(称为Mark Word)比如对象的Hash码,GC分代年龄,锁状态标志,是否启用偏向锁等等信息,另一类是对象类型信息,比如对象是那个类的实例。

7,我们在java堆中为对象分配了内存并且为对象的实例属性赋零值,设置对象的头信息之后对象是存在堆中了,那么我们怎么在java堆中找到我们刚创建的这个对象呢?答案是我们会把对象引用进行入栈操作,这样我们的程序就可以通过栈上的reference数据找到对象在堆中的地址并操作对象,具体是怎么找的呢?主流的方式主要有使用句柄和直接指针两种,如果使用句柄那么reference数据存的是句柄的地址,并且需要在堆中画出来一块内存存放句柄,这块内存称为“句柄池”,使用句柄访问对象的优点是解耦,对象被移动(比如垃圾回收过程中会移动对象)只需要更新句柄就可以了reference数据不需要修改。缺点是多了一次寻址操作,影响性能。如果使用直接指针的方式来访问对象,则reference数据存的对象的地址,优点是少了一次寻址操作,访问对象更快。缺点是对象被移动需要更新reference数据。

8,做完上述1~7操作在虚拟机的角度来说一个新的对象已经产生了,但是对java程序来说对象创建才刚开始因为构造函数既class文件中的方法还没有执行,执行init方法为对象初始化,这才是我们程序想要的对象 总结: 参考:《深入理解Java虚拟机第三版》 周志明 著