前言
什么情况下会触发类加载的进行呢?本文将结合代码demo谈谈几种情况,希望对大家有帮助。
类加载时机
什么情况需要开始类加载过程的第一阶段:加载?Java虚拟机规范中并没有进行强制约束,这点可以交给虚拟机的具体实现来*把握。但是对于初始化阶段,虚拟机规范则严格规定了以下几种情况必须立即对类进行初始化,如果类没有进行过初始化,则需要先触发其初始化。
创建类的实例
为了验证类加载,我们先配置一个JVM参数
-
-XX:+TraceClassLoading 监控类的加载
在IDE配置如下:
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. }
运行结果:
结论:
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. }
运行结果:
结论:
访问类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. }
运行结果:
结论:
访问类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. }
运行结果:
结论:
反射得到类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. }
运行结果:
看了运行结果,是不是发现,网上那道经典面试题(讲讲类的实例化顺序?)也很清晰啦。先父类静态变量/静态代码块-> 再子类静态变量/静态代码块->父类构造器->子类构造器
结论:
实例化子类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. }
运行结果:
结论:
虚拟机启动时,即使有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. }
运行结果:
分析:
- SingleTon.getInstance(),调用静态方法,触发SingleTon类加载。
- SingleTon类加载初始化,按顺序初始化静态变量。
- 先执行private static SingleTon singleTon = new SingleTon(); ,调用构造器后,count1,count2均为1;
- 按顺序执行 public static int count1; 没有赋值,所以count1依旧为1;
- 按顺序执行 public static int count2 = 0;所以count2变为0.
个人公众号
欢迎大家关注,大家一起学习,一起讨论。