笔记系列:JVM类链接和初始化

2022-06-05 10:39:06 浏览数 (1)

类在JVM的生命周期包括加载、链接和初始化。也就是loading、linking和initializing。前一篇博文已经介绍了Class文件和类加载器的内容,本文主要介绍另外的linking和initializing。同时也会记录分析一个关键性问题,就是静态变量和成员变量对于赋默认值和赋初始值的区别,这个区别会导致一些问题,为了避免这些问题,会用到volatile关键字。 说明:最近在补齐一些基础知识,这些内容都是一些旧的知识,所以文章以笔记的形式为主,将会与以往那种精耕细作的方式有所区别,为的是提高效率,快速学习。 关键字:java,jvm,linking,verification,preparation,resolution,initializing,volatile。

静态变量

Linking

1、Verification,文件校验,校验字节码是否符合JVM规范。

2、Preparation,给静态成员变量赋默认值。例如int默认值0,long默认值0,float默认值0.0。

3、resolution,解析。将类、方法、属性等符号引用解析为直接引用。Class文件字节码中介绍过,常量池中包含各种符号引用,解析就是把这里的符号引用转换为指向内存地址和指针的过程。很多都是动态绑定,只有new出来才会知道内存位置。

Initializing

1、调用类初始化代码<init>,对静态变量赋初始值

成员变量

private int m = 8;

上面都是针对静态变量。成员变量需要类先实例化以后才会执行。

1、实例化时会先给对象申请内存,在这时候给成员变量赋默认值0。

2、当申请完内存以后,会执行构造方法,才会赋值初始值8。

总结

1、load - 默认值 - 初始值

2、new - 申请内存 - 默认值 - 初始值

单例模式

DCL 单例,Double Check Loading 双重检查。

1、第一次检查,如果instance等于null,说明没有任何线程对它进行初始化。

2、第二次检查,就是在我上锁期间,可能有其他线程对instance进行了初始化,如果仍然为null,说明也没有任何线程对它进行初始化。我就可以在上锁内部放心的进行初始化了。

instance声明时是否需要加volatile?

如果没有加volatile,初始化逻辑在运行一半的时候,instance不为空了,它被赋值默认值了,但是还没有赋初始值。

这里仍旧是在使用上面的知识,静态变量在类linking和initializing的过程值的变化,成员变量也会在对象创建期间有一个默认值和初始值的状态的变化。

这时候,恰好,另外线程进来发现instance不为空,就不再初始化,直接使用instance了,而这时候这个线程拿到的其实是instance的默认值,不是初始值。就会造成严重的问题,相当于我的类的对象的所有成员全都还是默认值,例如金额long类型的,此时还是0呢,这时候另一个线程直接当它是有效的值去用,就产生问题了。

所以,我要保证,其他线程来的时候,要拿到instance的赋完初始值以后准备成熟的对象,再去使用就没问题了。那么如何保证呢?就是这个过程临时状态不被外界所侵扰,那么就需要volatile关键字来帮忙了。

volatile

volatile是如何保证对象创建时的临时状态不被外界所侵扰呢?它的内部原理实际上是指令重排。

我们编写了一个简单的类,main方法中只有对当前类的实例化,并用实例接收。使用IDEA插件查看字节码,查看到main函数的执行字节码:

1、new 申请了内存。

2、invokespecial,是调用构造方法init。

3、astore_1,把内存赋值给对象t。

指令重排就是第2和第3步顺序发生了交替,就是先把内存赋给了对象t,然后再构造方法init。

volatile关键字就是为了强制避免指令重排的情况,具体实现细节要研究Java内存模型。

0 人点赞