Java-类加载器-加载时机

青旅半醒 2023-10-09 05:25 173阅读 0赞

什么时候会出发类加载器?

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9odWFuZ2hhaXRhby5ibG9nLmNzZG4ubmV0_size_16_color_FFFFFF_t_70

创建类的实例

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

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

在IDE配置如下:

format_png

代码:

  1. package com.cdmt.java.annotation.historyhandler;
  2. /**
  3. *
  4. * @ClassName: ClassLoadInstance
  5. **/
  6. public class ClassLoadInstance {
  7. static {
  8. System.out.println("ClassLoadInstance类初始化时就会被执行!");
  9. }
  10. public ClassLoadInstance() {
  11. System.out.println("ClassLoadInstance构造函数!");
  12. }
  13. }
  14. class ClassLoadInstanceTest {
  15. public static void main(String[] args) {
  16. ClassLoadInstance instance = new ClassLoadInstance();
  17. }
  18. }

结果:

20190925112015790.png

结论:

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


访问类的静态变量

代码:

  1. package com.cdmt.java.annotation.historyhandler;
  2. /**
  3. *
  4. * @ClassName: ClassLoadInstance2
  5. **/
  6. public class ClassLoadStaticVariable {
  7. static {
  8. System.out.println("ClassLoadStaticVariable类初始化时就会被执行!");
  9. }
  10. public static int i = 100;
  11. public ClassLoadStaticVariable() {
  12. System.out.println("ClassLoadStaticVariable构造函数!");
  13. }
  14. }
  15. class ClassLoadStaticVariableTest {
  16. public static void main(String[] args) {
  17. System.out.println(ClassLoadStaticVariable.i);
  18. }
  19. }

结果:

20190925112319958.png

结论:

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

注意:

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


访问类的静态方法

代码:

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

结果:

20190925112516794.png

结论:

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


反射:

代码:

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

结果:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9odWFuZ2hhaXRhby5ibG9nLmNzZG4ubmV0_size_16_color_FFFFFF_t_70 1

结论:

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


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

代码:

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

结果:

20190925112906886.png

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

结论:

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


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

代码:

  1. package com.cdmt.java.annotation.historyhandler;
  2. /**
  3. *
  4. * @ClassName: ClassLoadTest
  5. **/
  6. public class ClassLoadTest {
  7. public static void main(String[] args) {
  8. System.out.println(ClassLoadSub.subNum);
  9. }
  10. }

结果:

20190925113038852.png

结论:

虚拟机启动时,即使有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. }

运行结果:

format_png 1

分析:

  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 条评论,173人围观)

还没有评论,来说两句吧...

相关阅读

    相关 ——

    虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作

    相关 Java

    类加载 -------------------- Java 的类加载阶段分为:加载、链接、初始化,而链接的过程中包括:验证、准备、解析 加载 将类的字节码载入方

    相关 时机

    以下内容为《深入理解Java虚拟机》的学习笔记: 类的生命周期: 加载-连接(验证-准备-解析)-初始化-使用-卸载 类加载时机: 1. 遇到new、getst