回归Java基础:触发类加载的六大时机

时间:2022-10-06 14:57:05

前言

什么情况下会触发类加载的进行呢?本文将结合代码demo谈谈几种情况,希望对大家有帮助。

类加载时机

什么情况需要开始类加载过程的第一阶段:加载?Java虚拟机规范中并没有进行强制约束,这点可以交给虚拟机的具体实现来*把握。但是对于初始化阶段,虚拟机规范则严格规定了以下几种情况必须立即对类进行初始化,如果类没有进行过初始化,则需要先触发其初始化。

回归Java基础:触发类加载的六大时机

创建类的实例

为了验证类加载,我们先配置一个JVM参数

  1. -XX:+TraceClassLoading 监控类的加载

在IDE配置如下:

回归Java基础:触发类加载的六大时机

demo代码:


1. ​​public class ClassLoadInstance {​​
2.
3. ​​ static {​​
4. ​​ System.out.println("ClassLoadInstance类初始化时就会被执行!");​​
5. ​​ }​​
6.
7. ​​ public ClassLoadInstance() {​​
8. ​​ System.out.println("ClassLoadInstance构造函数!");​​
9. ​​ }​​
10. ​​}​​
11.
12. ​​public class ClassLoadTest {​​
13.
14. ​​ public static void main(String[] args) {​​
15. ​​ ClassLoadInstance instance = new ClassLoadInstance();​​
16. ​​ }​​
17. ​​}​​

运行结果:

回归Java基础:触发类加载的六大时机

结论:

new ClassLoadInstance实例时,发现ClassLoadInstance被加载了,因此 new创建实例对象,会触发类加载进行。

访问类的静态变量

demo代码:


1. ​​public class ClassLoadStaticVariable {​​
2.
3. ​​ static {​​
4. ​​ System.out.println("ClassLoadStaticVariable类初始化时就会被执行!");​​
5. ​​ }​​
6.
7. ​​ public static int i = 100;​​
8.
9. ​​ public ClassLoadStaticVariable() {​​
10. ​​ System.out.println("ClassLoadStaticVariable构造函数!");​​
11. ​​ }​​
12.
13. ​​}​​
14.
15. ​​public class ClassLoadTest {​​
16.
17. ​​ public static void main(String[] args) {​​
18. ​​ System.out.println(ClassLoadStaticVariable.i);​​
19. ​​ }​​
20. ​​}​​

运行结果:

回归Java基础:触发类加载的六大时机

结论:

访问类ClassLoadStaticVariable的静态变量i时,发现ClassLoadStaticVariable类被加载啦,因此访问类的静态变量会触发类加载。

注意:

访问final修饰的静态变量时,不会触发类加载,因为在编译期已经将此常量放在常量池了。

访问类的静态方法

demo代码:


1. ​​public class ClassLoadStaticMethod {​​
2.
3. ​​ static {​​
4. ​​ System.out.println("ClassLoadStaticMethod类初始化时就会被执行!");​​
5. ​​ }​​
6.
7. ​​ public static void method(){​​
8. ​​ System.out.println("静态方法被调用");​​
9. ​​ }​​
10.
11. ​​ public ClassLoadStaticMethod() {​​
12. ​​ System.out.println("ClassLoadStaticMethod构造函数!");​​
13. ​​ }​​
14.
15. ​​}​​
16.
17. ​​public class ClassLoadTest {​​
18.
19. ​​ public static void main(String[] args) {​​
20. ​​ ClassLoadStaticMethod.method();​​
21. ​​ }​​
22. ​​}​​

运行结果:

回归Java基础:触发类加载的六大时机

结论:

访问类ClassLoadStaticMethod的静态方法method时,发现ClassLoadStaticMethod类被加载啦,因此访问类的静态方法会触发类加载。

反射

demo代码:


1. ​​package classload;​​
2.
3. ​​public class ClassLoadStaticReflect {​​
4.
5. ​​ static {​​
6. ​​ System.out.println("ClassLoadStaticReflect类初始化时就会被执行!");​​
7. ​​ }​​
8.
9. ​​ public static void method(){​​
10. ​​ System.out.println("静态方法被调用");​​
11. ​​ }​​
12.
13. ​​ public ClassLoadStaticReflect() {​​
14. ​​ System.out.println("ClassLoadStaticReflect构造函数!");​​
15. ​​ }​​
16.
17. ​​}​​
18.
19. ​​public class ClassLoadTest {​​
20.
21. ​​ public static void main(String[] args) throws ClassNotFoundException {​​
22. ​​ Class.forName("classload.ClassLoadStaticReflect");​​
23. ​​ }​​
24. ​​}​​

运行结果:

回归Java基础:触发类加载的六大时机

结论:

反射得到类ClassLoadStaticReflect时,发现ClassLoadStaticReflect类被加载啦,因此反射会触发类加载。

当初始化一个类时,发现其父类还未初始化,则先触发父类的初始化

demo代码:


1. ​​//父类​​
2. ​​public class ClassLoadSuper {​​
3. ​​ static {​​
4. ​​ System.out.println("ClassLoadSuper类初始化时就会被执行!这是父类");​​
5. ​​ }​​
6.
7. ​​ public static int superNum = 100;​​
8.
9. ​​ public ClassLoadSuper() {​​
10. ​​ System.out.println("父类ClassLoadSuper构造函数!");​​
11. ​​ }​​
12. ​​}​​
13. ​​//子类​​
14. ​​public class ClassLoadSub extends ClassLoadSuper {​​
15.
16. ​​ static {​​
17. ​​ System.out.println("ClassLoadSub类初始化时就会被执行!这是子类");​​
18. ​​ }​​
19.
20. ​​ public static int subNum = 100;​​
21.
22. ​​ public ClassLoadSub() {​​
23. ​​ System.out.println("子类ClassLoadSub构造函数!");​​
24. ​​ }​​
25.
26. ​​}​​
27.
28. ​​public class ClassLoadTest {​​
29.
30. ​​ public static void main(String[] args) throws ClassNotFoundException {​​
31. ​​ ClassLoadSub classLoadSub = new ClassLoadSub();​​
32. ​​ }​​
33. ​​}​​

运行结果:

回归Java基础:触发类加载的六大时机看了运行结果,是不是发现,网上那道经典面试题(讲讲类的实例化顺序?)也很清晰啦。先父类静态变量/静态代码块-> 再子类静态变量/静态代码块->父类构造器->子类构造器

结论:

实例化子类ClassLoadSub的时候,发现父类ClassLoadSuper先被加载,因此当初始化一个类时,发现其父类还未初始化,则先触发父类的初始化

虚拟机启动时,定义了main()方法的那个类先初始化

demo代码:


1. ​​package classload;​​
2.
3. ​​public class ClassLoadTest {​​
4.
5. ​​ public static void main(String[] args) {​​
6. ​​ System.out.println(ClassLoadSub.subNum);​​
7. ​​ }​​
8. ​​}​​

运行结果:

回归Java基础:触发类加载的六大时机

结论:

虚拟机启动时,即使有ClassLoadSub,ClassLoadSuper,ClassLoadTest等类被加载, 但ClassLoadTest最先被加载,即定义了main()方法的那个类会先触发类加载。

练习与小结

触发类加载的六大时机,我们都分析完啦,是不是不做个题都觉得意犹未尽呢?接下来,我们来分析类加载一道经典面试题吧。


1. ​​class SingleTon {  ​​
2. ​​ private static SingleTon singleTon = new SingleTon(); ​​
3. ​​ public static int count1; ​​
4. ​​ public static int count2 = 0; ​​
5.
6. ​​ private SingleTon() { ​​
7. ​​ count1++; ​​
8. ​​ count2++; ​​
9. ​​ } ​​
10.
11. ​​ public static SingleTon getInstance() { ​​
12. ​​ return singleTon; ​​
13. ​​ } ​​
14. ​​} ​​
15.
16. ​​public class ClassLoadTest { ​​
17. ​​ public static void main(String[] args) { ​​
18. ​​ SingleTon singleTon = SingleTon.getInstance(); ​​
19. ​​ System.out.println("count1=" + singleTon.count1); ​​
20. ​​ System.out.println("count2=" + singleTon.count2); ​​
21. ​​ } ​​
22. ​​} ​​

运行结果:

回归Java基础:触发类加载的六大时机

分析:

  1. SingleTon.getInstance(),调用静态方法,触发SingleTon类加载。
  2. SingleTon类加载初始化,按顺序初始化静态变量。
  3. 先执行private static SingleTon singleTon = new SingleTon(); ,调用构造器后,count1,count2均为1;
  4. 按顺序执行 public static int count1; 没有赋值,所以count1依旧为1;
  5. 按顺序执行 public static int count2 = 0;所以count2变为0.

个人公众号

回归Java基础:触发类加载的六大时机

欢迎大家关注,大家一起学习,一起讨论。