JVM 知识 -- 对象初始化过程
本文最后更新于:3 天前
-
加载字节码中的文件到内存中的方法区
虚拟机遇到 new 指令,首先去检查 new 指令的参数是否能在常量池中定位到一个类的符号引用,并且这个符号引用代表的类是否已被加载,解析和初始化过
如果没有,那么先执行相应的类加载过程
-
初始化 static 相关的成员变量和静态方法,称为类初始化(全部都在方法区)
-
先把所有的 static 部分(成员与方法)申请空间,然后再给每一个 static 成员由上至下分配初始值
-
分配完空间之后,会有默认值,而不是指定的值
-
这时才执行显式赋值,静态代码块对变量进行赋值,执行顺序与代码一致
-
-
通过 new 在堆内存中申请对象的内存空间,取得首地址
在类加载检查通过后,要创建的对象所需的内存大小已经确定了,虚拟机将一块确定大小的内存从堆中划分出来分配给对象
假设堆中所有用过的内存放在一边,空闲的内存放在另外一边,中间放一个指针作为分界点的指示器,那分配内存就是移动这个指针的过程,这种内存分配方式叫"指针碰撞"
但如果堆中的内存,空闲的和使用过的是交互相错的,不是规整的,那么虚拟机必须维护一个队列,记录哪些内存块是可用的,分配的时候,查找表找到一块足够大的空间划分给对象,并更新表信息,这种方式叫"空闲列表"
选择哪种分配方式是由堆是否规整决定的,而Java堆是否规则是由所采用的垃圾收集器是否带有压缩整理功能决定的
-
还需要考虑的一个问题是,在虚拟机中创建对象是否是一个非常频繁的行为
即便是采用"指针碰撞"的方式,在并发的情况下,修改指针也不是线程安全的
对于这个问题,有两种解决方法:
-
第一,对分配内存空间的操作进行同步处理 – 实际上虚拟机采用 CAS 加自旋的方式保证了更新操作的原子性
-
第二,每个线程在堆中预先分配一小块内存,叫本地线程分配缓冲(TLAB)
哪个线程要分配内存,就在哪个线程的 TLAB 上分配,只有 TLAB 用完并分配新的 TLAB 时,才需要同步锁定
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),如果使用 TLAB,这一过程也可以提前至 TLAB 分配时进行
这一操作保证了对象的实例字段在 Java 代码中可以不赋初始值就可以直接使用,程序能访问到这些字段的数据类型所对应的零值
-
-
-
接下来,虚拟机要对对象进行必要的设置,例如这个对象属于哪个类,如何才能找到类的元数据信息,对象的哈希码,对象的 GC 分代年龄等信息
到现在为止,从 JVM 的角度来看,一个新的对象已经产生了
但从 Java 程序来看,还需要对其执行 init 方法,所有字段都还是零值
一般来说,执行 new 指令之后会接着执行 init 方法,把对象安照程序员的意愿进行初始化
-
构造方法压栈,依次执行以下的操作
-
把类的非静态成员变量加载到堆内存中并分配空间同时默认初始化,非静态成员方法加载到方法区中并分配空间,然后由上到下进行非静态成员变量的初始化
-
用构造代码块和显示赋值对成员变量进行赋值,执行顺序与代码顺序一致
-
执行构造函数
-
-
整个对象初始化完成,返回首地址值
如果有父类,则:
先进行父类静态初始化,然后进行子类静态初始化(加载类文件的先后)
在堆内存空间分配完毕后,然后父类对象初始化,在进行子类对象初始化
(堆内存的创建,在父类对象的基础上加东西变成子类对象)
本博客所有文章除特别声明外,均采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 。转载请注明出处!