java 类加载 实例变量_从两种单例模式谈java类加载过程中静态变量的初始化问题...

本文通过实例探讨了Java类加载过程中静态变量初始化的问题,分析了饿汉式和静态内部类模式的区别。实验结果显示,类加载并不立即初始化静态变量,而是在遇到特定指令时才会初始化。静态内部类模式相比饿汉式,实例化稍晚,但仅在get方法体执行时产生差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

之前的文章里有总结过java的单例怎么写,具体链接java中的7种单例模式。

经常听到,类的static变量在类加载时就会初始化,于是有了常说的两种单例模式的对比:饿汉式和静态内部类模式。通常的说法是,两种都是支持线程安全的(关于怎么个安全法请看我上面的链接),饿汉式不被推荐是因为会提前初始化,占用一部分内存。

那我们就用代码说话。

验证

根据jvm加载class文件的过程,先是讲class文件流加载到内存,然后验证是否符合class的文件结构,接着生成对应的Class对象丢到方法区。那么,当我们拿到一个类的Class对象时,说明类加载成功了。

Instance.java

public class Instance {

private static Instance instance = new Instance();

private Instance(){

System.out.println("instance be alloc");

}

public static Instance get(){

System.out.println("instance be called");

return instance;

}

}

入口main方法:

public static void main(String[] a){

// 能拿到对应的class类说明已经加载到虚拟机中

Class instanceClass = Instance.class;

System.out.println("Instance load over"+instanceClass!=null);

}

按照“类在加载后就会实例化static变量”的说法,那么“”instance be alloc”一定会打印出来。我们看下结果:

true

很遗憾,即使对象加载了,Instance对象也并没有被实例。接着调用Instance.get()方法:

public class Main {

public static void main(String[] a){

// 能拿到对应的class类说明已经加载到虚拟机中

Class instanceClass = Instance.class;

System.out.println("Instance load over"+instanceClass!=null);

Instance.get();

}

}

结果:

true

instance be alloc

instance be called

是的,当调用Instance.get()的时候,我们的Instance实例化了对象出来。但是请注意, System.out.println(“instance be called”);这行在return instance之前,说明调用时序是get()-》alloc()-》return

以上的过程,验证了类并不是在加载完之后就会实例static变量。那到底什么时候才会初始化呢?

1.当遇到new,getstatic,putstatic或者invokestatic这四条字节码指令的时候,如果该类没有进行

初始化,则需要先初始化.这四条指令对应的是实例化对象,获取一个静态变量,设置一个静态变量(常量

放在常量池中,不会触发),或者调用静态方法的时候.

2.当时候反射包的方法对类进行反射调用的时候

3.当初始化一个类的时候,发现该类的父类还没有进行初始化,则初始其父类

4.当jvm启动的时候,当用户指定执行一个主类(就是包含main的那个类),虚拟机会先初始化这个类.

很明显,上述的例子是因为满足第一条,执行static方法的时候编译器会生成invokestatic指令,这时候instance没有初始化,所以会执行Instance的构造方法,然后在return返回。

饿汉式和静态内部类式的区别

public class Instance1 {

private static class Holder{

private static Instance1 instance = new Instance1();

}

private Instance1(){

System.out.println("instance1 alloc");

}

public static Instance1 get(){

System.out.println("instance1 called");

//特意不返回instance

return null;

}

}

注意,在get方法特意返回了null.

public class Main {

public static void main(String[] a){

// 能拿到对应的class类说明已经加载到虚拟机中

Class instanceClass = Instance1.class;

System.out.println("Instance load over"+instanceClass!=null);

Instance1.get();

}

}

执行结果:

true

instance1 called

发现Instance1的构造方法并没有被调用。让get()返回instance实例再打印:

public class Instance1 {

private static class Holder{

private static Instance1 instance = new Instance1();

}

private Instance1(){

System.out.println("instance1 alloc");

}

public static Instance1 get(){

System.out.println("instance1 called");

return Holder.instance;

}

}

结果是:

true

instance1 called

instance1 alloc

这下总算实例化了!但是!强调出来,顺序是get()-》print-》alloc()-》return

从上面讲到的初始化的时机来印证,当执行get()方法时,Instance类并没有静态变量,当执行Hoder.instance的时候,是触发了对instance静态变量的引用,编译时生成的invokestatic(内部类对外部类私有变量的引用,见之前的文章java 内类和外类的关系)触发,使得instance作为Holder的静态变量被初始化。

比较两个过程:

饿汉式:get-》alloc-》return

静态内部类:get-》print(或者其他的方法体)-》alloc-》return

发现后者是多了一个过程,那就是在get()和return之间的代码执行。

结论

首先,类加载的时候就会初始化静态变量,这是不准确的;其次静态内部类式确实比饿汉式晚实例化,但是仅限于get和return之间的代码块,如果这部分代码块很简单甚至没有,那么两者的差距微乎其微。

如果只是写一个简单单例,两者几乎不存在什么性能上的差异。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值