分享

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

 捡田螺的小男孩 2020-06-02

前言

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

类加载时机

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

创建类的实例

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

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

在IDE配置如下:

demo代码:

  1. public class ClassLoadInstance {


  2. static {

  3. System.out.println("ClassLoadInstance类初始化时就会被执行!");

  4. }


  5. public ClassLoadInstance() {

  6. System.out.println("ClassLoadInstance构造函数!");

  7. }

  8. }


  9. public class ClassLoadTest {


  10. public static void main(String[] args) {

  11. ClassLoadInstance instance = new ClassLoadInstance();

  12. }

  13. }

运行结果:

结论:

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

访问类的静态变量

demo代码:

  1. public class ClassLoadStaticVariable {


  2. static {

  3. System.out.println("ClassLoadStaticVariable类初始化时就会被执行!");

  4. }


  5. public static int i = 100;


  6. public ClassLoadStaticVariable() {

  7. System.out.println("ClassLoadStaticVariable构造函数!");

  8. }


  9. }


  10. public class ClassLoadTest {


  11. public static void main(String[] args) {

  12. System.out.println(ClassLoadStaticVariable.i);

  13. }

  14. }

运行结果:

结论:

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

注意:

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

访问类的静态方法

demo代码:

  1. public class ClassLoadStaticMethod {


  2. static {

  3. System.out.println("ClassLoadStaticMethod类初始化时就会被执行!");

  4. }


  5. public static void method(){

  6. System.out.println("静态方法被调用");

  7. }


  8. public ClassLoadStaticMethod() {

  9. System.out.println("ClassLoadStaticMethod构造函数!");

  10. }


  11. }


  12. public class ClassLoadTest {


  13. public static void main(String[] args) {

  14. ClassLoadStaticMethod.method();

  15. }

  16. }

运行结果:

结论:

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

反射

demo代码:

  1. package classload;


  2. public class ClassLoadStaticReflect {


  3. static {

  4. System.out.println("ClassLoadStaticReflect类初始化时就会被执行!");

  5. }


  6. public static void method(){

  7. System.out.println("静态方法被调用");

  8. }


  9. public ClassLoadStaticReflect() {

  10. System.out.println("ClassLoadStaticReflect构造函数!");

  11. }


  12. }


  13. public class ClassLoadTest {


  14. public static void main(String[] args) throws ClassNotFoundException {

  15. Class.forName("classload.ClassLoadStaticReflect");

  16. }

  17. }

运行结果:

结论:

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

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

demo代码:

  1. //父类

  2. public class ClassLoadSuper {

  3. static {

  4. System.out.println("ClassLoadSuper类初始化时就会被执行!这是父类");

  5. }


  6. public static int superNum = 100;


  7. public ClassLoadSuper() {

  8. System.out.println("父类ClassLoadSuper构造函数!");

  9. }

  10. }

  11. //子类

  12. public class ClassLoadSub extends ClassLoadSuper {


  13. static {

  14. System.out.println("ClassLoadSub类初始化时就会被执行!这是子类");

  15. }


  16. public static int subNum = 100;


  17. public ClassLoadSub() {

  18. System.out.println("子类ClassLoadSub构造函数!");

  19. }


  20. }


  21. public class ClassLoadTest {


  22. public static void main(String[] args) throws ClassNotFoundException {

  23. ClassLoadSub classLoadSub = new ClassLoadSub();

  24. }

  25. }

运行结果:

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

结论:

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

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

demo代码:

  1. package classload;


  2. public class ClassLoadTest {


  3. public static void main(String[] args) {

  4. System.out.println(ClassLoadSub.subNum);

  5. }

  6. }

运行结果:

结论:

虚拟机启动时,即使有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. private SingleTon() {

  6. count1++;

  7. count2++;

  8. }


  9. public static SingleTon getInstance() {

  10. return singleTon;

  11. }

  12. }


  13. public class ClassLoadTest {

  14. public static void main(String[] args) {

  15. SingleTon singleTon = SingleTon.getInstance();

  16. System.out.println("count1=" + singleTon.count1);

  17. System.out.println("count2=" + singleTon.count2);

  18. }

  19. }

运行结果:

分析:

  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.

个人公众号

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

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多