• 【JVM学习03】类加载与字节码技术


    1、 字节码指令

    1)异常处理

    try-catch

    public class Code_15_TryCatchTest { 
    
        public static void main(String[] args) { 
            int i = 0;
            try { 
                i = 10;
            }catch (Exception e) { 
                i = 20;
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    对应字节码指令

    Code:
         stack=1, locals=3, args_size=1
            0: iconst_0
            1: istore_1
            2: bipush        10
            4: istore_1
            5: goto          12
            8: astore_2
            9: bipush        20
           11: istore_1
           12: return
         //多出来一个异常表
         Exception table:
            from    to  target type
                2     5     8   Class java/lang/Exception
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 可以看到多出来一个 Exception table 的结构,[from, to) 是前闭后开(也就是检测 2~4 行)的检测范围,一旦这个范围内的字节码执行出现异常,则通过 type 匹配异常类型,如果一致,进入 target 所指示行号
    • 8 行的字节码指令 astore_2 是将异常对象引用存入局部变量表的 2 号位置(为 e )

    多个 single-catch

    public class Code_16_MultipleCatchTest { 
    
        public static void main(String[] args) { 
            int i = 0;
            try { 
                i = 10;
            }catch (ArithmeticException e) { 
                i = 20;
            }catch (Exception e) { 
                i = 30;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    对应的字节码

    Code:
         stack=1, locals=3, args_size=1
            0: iconst_0
            1: istore_1
            2: bipush        10
            4: istore_1
            5: goto          19
            8: astore_2
            9: bipush        20
           11: istore_1
           12: goto          19
           15: astore_2
           16: bipush        30
           18: istore_1
           19: return
         Exception table:
            from    to  target type
                2     5     8   Class java/lang/ArithmeticException
                2     5    15   Class java/lang/Exception
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 因为异常出现时,只能进入 Exception table 中一个分支,所以局部变量表 slot 2 位置被共用

    finally

    public class Code_17_FinallyTest { 
        
        public static void main(String[] args) { 
            int i = 0;
            try { 
                i = 10;
            } catch (Exception e) { 
                i = 20;
            } finally { 
                i = 30;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    对应字节码

    Code:
         stack=1, locals=4, args_size=1
            0: iconst_0
            1: istore_1
            // try块
            2: bipush        10
            4: istore_1
            // try块执行完后,会执行finally    
            5: bipush        30
            7: istore_1
            8: goto          27
           // catch块     
           11: astore_2 // 异常信息放入局部变量表的2号槽位
           12: bipush        20
           14: istore_1
           // catch块执行完后,会执行finally        
           15: bipush        30
           17: istore_1
           18: goto          27
           // 出现异常,但未被 Exception 捕获,会抛出其他异常,这时也需要执行 finally 块中的代码   
           21: astore_3
           22: bipush        30
           24: istore_1
           25: aload_3
           26: athrow  // 抛出异常
           27: return
         Exception table:
            from    to  target type
                2     5    11   Class java/lang/Exception
                2     5    21   any
               11    15    21   any
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 可以看到 finally 中的代码被复制了 3 份,分别放入 try 流程,catch 流程以及 catch 剩余的异常类型流程
    • 注意:虽然从字节码指令看来,每个块中都有 finally 块,但是 finally 块中的代码只会被执行一次

    finally 中的 return

    public class Code_18_FinallyReturnTest { 
    
        public static void main(String[] args) { 
            int i = Code_18_FinallyReturnTest.test();
            // 结果为 20
            System.out.println(i);
        }
    
        public static int test() { 
            int i;
            try { 
                i = 10;
                return i;
            } finally { 
                i = 20;
                return i;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    对应字节码

    Code:
         stack=1, locals=3, args_size=0
            0: bipush        10
            2: istore_0
            3: iload_0
            4: istore_1  // 暂存返回值
            5: bipush        20
            7: istore_0
            8: iload_0
            9: ireturn	// ireturn 会返回操作数栈顶的整型值 20
           // 如果出现异常,还是会执行finally 块中的内容,没有抛出异常
           10: astore_2
           11: bipush        20
           13: istore_0
           14: iload_0
           15: ireturn	// 这里没有 athrow 了,也就是如果在 finally 块中如果有返回操作的话,且 try 块中出现异常,会吞掉异常!
         Exception table:
            from    to  target type
                0     5    10   any
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 由于 finally 中的 ireturn 被插入了所有可能的流程,因此返回结果肯定以finally的为准
    • 至于字节码中第 2 行,似乎没啥用,且留个伏笔,看下个例子
    • 跟上例中的 finally 相比,发现没有 athrow 了,这告诉我们:如果在 finally 中出现了 return,会吞掉异常
    • 所以不要在finally中进行返回操作

    被吞掉的异常

    public static int test() { 
          int i;
          try { 
             i = 10;
             //  这里应该会抛出异常
             i = i/0;
             return i;
          } finally { 
             i = 20;
             return i;
          }
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    会发现打印结果为 20 ,并未抛出异常

    finally 不带 return

    public static int test() { 
    		int i = 10;
    		try { 
    			return i;
    		} finally { 
    			i = 20;
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    对应字节码

    Code:
         stack=1, locals=3, args_size=0
            0: bipush        10
            2: istore_0 // 赋值给i 10
            3: iload_0	// 加载到操作数栈顶
            4: istore_1 // 加载到局部变量表的1号位置
            5: bipush        20
            7: istore_0 // 赋值给i 20
            8: iload_1 // 加载局部变量表1号位置的数10到操作数栈
            9: ireturn // 返回操作数栈顶元素 10
           10: astore_2
           11: bipush        20
           13: istore_0
           14: aload_2 // 加载异常
           15: athrow // 抛出异常
         Exception table:
            from    to  target type
                3     5    10   any
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2)Synchronized

    public class Code_19_SyncTest { 
    
        public static void main(String[] args) { 
            Object lock = new Object();
            synchronized (lock) { 
                System.out.println("ok");
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    对应字节码

    Code:
         stack=2, locals=5, args_size=1
            0: bipush        10
            2: istore_1
            3: new           #2                  // class com/nyima/JVM/day06/Lock
            6: dup //复制一份,放到操作数栈顶,用于构造函数消耗
            7: invokespecial #3                  // Method com/nyima/JVM/day06/Lock."":()V
           10: astore_2 //剩下的一份放到局部变量表的2号位置
           11: aload_2 //加载到操作数栈
           12: dup //复制一份,放到操作数栈,用于加锁时消耗
           13: astore_3 //将操作数栈顶元素弹出,暂存到局部变量表的三号槽位。这时操作数栈中有一份对象的引用
           14: monitorenter //加锁
           //锁住后代码块中的操作    
           15: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
           18: iload_1
           19: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
           //加载局部变量表中三号槽位对象的引用,用于解锁    
           22: aload_3    
           23: monitorexit //解锁
           24: goto          34
           //异常操作    
           27: astore        4
           29: aload_3
           30: monitorexit //解锁
           31: aload         4
           33: athrow
           34: return
         //可以看出,无论何时出现异常,都会跳转到27行,将异常放入局部变量中,并进行解锁操作,然后加载异常并抛出异常。      
         Exception table:
            from    to  target type
               15    24    27   any
               27    31    27   any
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    2、编译期处理

    所谓的 语法糖 ,其实就是指 java 编译器把 .java 源码编译为 .class 字节码的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担,算是 java 编译器给我们的一个额外福利 注意,以下代码的分析,借助了 javap 工具,idea 的反编译功能,idea 插件 jclasslib 等工具。另外, 编译器转换的结果直接就是 class 字节码,只是为了便于阅读,给出了 几乎等价 的 java 源码方式,并不是编译器还会转换出中间的 java 源码,切记。

    1)默认构造器

    public class Candy1 { 
    
    }
    
    • 1
    • 2
    • 3

    经过编译期优化后

    public class Candy1 { 
       // 这个无参构造器是java编译器帮我们加上的
       public Candy1() { 
          // 即调用父类 Object 的无参构造方法,即调用 java/lang/Object." ":()V
          super();
       }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2)自动拆装箱

    基本类型和其包装类型的相互转换过程,称为拆装箱 在 JDK 5 以后,它们的转换可以在编译期自动完成

    public class Candy2 { 
       public static void main(String[] args) { 
          Integer x = 1;
          int y = x;
       }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    转换过程如下

    public class Candy2 { 
       public static void main(String[] args) { 
          // 基本类型赋值给包装类型,称为装箱
          Integer x = Integer.valueOf(1);
          // 包装类型赋值给基本类型,称谓拆箱
          int y = x.intValue();
       }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3)泛型擦除

    泛型也是在 JDK 5 开始加入的特性,但 java 在编译泛型代码后会执行泛型擦除的动作,即泛型信息在编译为字节码之后就丢失了,实际的类型都当做了 Object 类型来处理

    public class Candy3 { 
       public static void main(String[] args) { 
          List<Integer> list = new ArrayList<Integer>();
          list.add(10);
          Integer x = list.get(0);
       }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    对应字节码

    Code:
        stack=2, locals=3, args_size=1
           0: new           #2                  // class java/util/ArrayList
           3: dup
           4: invokespecial #3                  // Method java/util/ArrayList." ":()V
           7: astore_1
           8: aload_1
           9: bipush        10
          11: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
          // 这里进行了泛型擦除,实际调用的是add(Objcet o)
          14: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
    
          19: pop
          20: aload_1
          21: iconst_0
          // 这里也进行了泛型擦除,实际调用的是get(Object o)   
          22: invokeinterface #6,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
    // 这里进行了类型转换,将 Object 转换成了 Integer
          27: checkcast     #7                  // class java/lang/Integer
          30: astore_2
          31: return
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    所以调用 get 函数取值时,有一个类型转换的操作。

    Integer x = (Integer) list.get(0);
    
    • 1

    如果要将返回结果赋值给一个 int 类型的变量,则还有自动拆箱的操作

    int x = (Integer) list.get(0).intValue();
    
    • 1

    使用反射可以得到,参数的类型以及泛型类型。泛型反射代码如下:

    
        public static void main(String[] args) throws NoSuchMethodException { 
            // 1. 拿到方法
            Method method = Code_20_ReflectTest.class.getMethod("test", List.class, Map.class);
            // 2. 得到泛型参数的类型信息
            Type[] types = method.getGenericParameterTypes();
            for(Type type : types) { 
                // 3. 判断参数类型是否,带泛型的类型。
                if(type instanceof ParameterizedType) { 
                    ParameterizedType parameterizedType = (ParameterizedType) type;
    
                    // 4. 得到原始类型
                    System.out.println("原始类型 - " + parameterizedType.getRawType());
                    // 5. 拿到泛型类型
                    Type[] arguments = parameterizedType.getActualTypeArguments();
                    for(int i = 0; i &lt; arguments.length; i++) { 
                        System.out.printf("泛型参数[%d] - %s\n", i, arguments[i]);
                    }
                }
            }
        }
    
        public Set&lt;Integer&gt; test(List&lt;String&gt; list, Map&lt;Integer, Object&gt; map) { 
            return null;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    输出:

    原始类型 - interface java.util.List
    泛型参数[0] - class java.lang.String
    原始类型 - interface java.util.Map
    泛型参数[0] - class java.lang.Integer
    泛型参数[1] - class java.lang.Object
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3、类加载阶段

    1)加载

    • 将类的字节码载入方法区(1.8后为元空间,在本地内存中)中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 field 有:

      • _java_mirror 即 java 的类镜像,例如对 String 来说,它的镜像类就是 String.class,作用是把 klass 暴露给 java 使用
      • _super 即父类
      • _fields 即成员变量
      • _methods 即方法
      • _constants 即常量池
      • _class_loader 即类加载器
      • _vtable 虚方法表
      • _itable 接口方法
    • 如果这个类还有父类没有加载,先加载父类

    • 加载和链接可能是交替运行的

    image-20220720101752010

    • instanceKlass保存在方法区。JDK 8以后,方法区位于元空间中,而元空间又位于本地内存中
    • _java_mirror则是保存在堆内存中
    • InstanceKlass和.class(JAVA镜像类)互相保存了对方的地址
    • 类的对象在对象头中保存了.class的地址。让对象可以通过其找到方法区中的instanceKlass,从而获取类的各种信息
      注意

    instanceKlass 这样的【元数据】是存储在方法区(1.8 后的元空间内),但 _java_mirror 是存储在堆中

    2) 链接

    验证 验证类是否符合 JVM规范,安全性检查

    用 UE 等支持二进制的编辑器修改 HelloWorld.class 的魔数,在控制台运行 准备 为 static 变量分配空间,设置默认值

    准备
    为 static 变量分配空间,设置默认值

    • static 变量在 JDK 7 之前存储于 instanceKlass 末尾(方法区),从 JDK 7 开始,存储于 _java_mirror 末尾(堆中)
    • static 变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成
    • 如果 static 变量是 final 的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
    • 如果 static 变量是 final 的,但属于引用类型,那么赋值也会在初始化阶段完成

    解析

    类加载时,类的字节码载入方法区。解析时,将常量池中的符号引用解析为直接引用

    public class Code_22_AnalysisTest { 
    
    
        public static void main(String[] args) throws ClassNotFoundException, IOException { 
            ClassLoader classLoader = Code_22_AnalysisTest.class.getClassLoader();
            Class<?> c = classLoader.loadClass("P3.C");
    
            // new C();
            System.in.read();
        }
    
    }
    
    class C { 
        D d = new D();
    }
    
    class D { 
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 使用类加载器ClassLoader的classLoad方法,只会加载C这个类。
    • 加载即将类的字节码载入方法区中,但是也仅仅是字节码,不会对字节码做处理
    • 所以常量池中,类D仅仅是一个符号引用,并不知道它要引用哪个地址

    但是

    • 使用new C(),new会触发加载、解析
    • 解析会将常量池中的符号引用解析为直接引用

    3)初始化

    ()v 方法

    初始化即调用 ()V ,虚拟机会保证这个类的『构造方法』的线程安全

    类触发初始化的情况

    概括得说,类初始化是【懒惰的】

    • main 方法所在的类,总会被首先初始化
    • 首次访问这个类的静态变量或静态方法时-
    • 子类初始化,如果父类还没初始化,会引发-
    • 子类访问父类的静态变量,只会触发父类的初始化-
    • Class.forName
    • new 会导致初始化

    不会导致类初始化的情况

    • 访问类的 static final 静态常量(基本类型和字符串)不会触发初始化
    • 类对象.class 不会触发初始化(准备阶段已经创建好class文件)
    • 创建该类的数组不会触发初始化
    public class Load1 { 
        static { 
            System.out.println("main init");
        }
        public static void main(String[] args) throws ClassNotFoundException { 
            // 1. 静态常量(基本类型和字符串)不会触发初始化
    //         System.out.println(B.b);
            // 2. 类对象.class 不会触发初始化
    //         System.out.println(B.class);
            // 3. 创建该类的数组不会触发初始化
    //         System.out.println(new B[0]);
            // 4. 不会初始化类 B,但会加载 B、A
    //         ClassLoader cl = Thread.currentThread().getContextClassLoader();
    //         cl.loadClass("cn.ali.jvm.test.classload.B");
            // 5. 不会初始化类 B,但会加载 B、A
    //         ClassLoader c2 = Thread.currentThread().getContextClassLoader();
    //         Class.forName("cn.ali.jvm.test.classload.B", false, c2);
    
    
            // 1. 首次访问这个类的静态变量或静态方法时
    //         System.out.println(A.a);
            // 2. 子类初始化,如果父类还没初始化,会引发
    //         System.out.println(B.c);
            // 3. 子类访问父类静态变量,只触发父类初始化
    //         System.out.println(B.a);
            // 4. 会初始化类 B,并先初始化类 A
    //         Class.forName("cn.ali.jvm.test.classload.B");
        }
    
    }
    
    
    class A { 
        static int a = 0;
        static { 
            System.out.println("a init");
        }
    }
    class B extends A { 
        final static double b = 5.0;
        static boolean c = false;
        static { 
            System.out.println("b init");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    4)练习

    从字节码分析,使用 a,b,c 这三个常量是否会导致 E 初始化

    public class Load2 { 
    
        public static void main(String[] args) { 
            System.out.println(E.a);
            System.out.println(E.b);
            // 会导致 E 类初始化,因为 Integer 是包装类,初始化后会调用Integer.valueOf()进行赋值
            System.out.println(E.c);
        }
    }
    
    class E { 
        public static final int a = 10;
        public static final String b = "hello";
        public static final Integer c = 20;
    
        static { 
            System.out.println("E cinit");
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    典型应用

    • 完成懒惰初始化单例模式
    public class Singleton { 
    
        private Singleton() {  } 
        // 内部类中保存单例
        private static class LazyHolder {  
            static final Singleton INSTANCE = new Singleton(); 
        }
        // 第一次调用 getInstance 方法,才会导致内部类加载和初始化其静态成员 
        public static Singleton getInstance() {  
            return LazyHolder.INSTANCE; 
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    以上的实现特点是:

    • 懒惰实例化
    • 初始化时的线程安全是有保障的

    5、类加载器

    类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远超类加载阶段 对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个 Java 虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等!

    以JDK 8为例

    image-20220720172820826

    1)启动类的加载器

    可通过在控制台输入指令,使得类被启动类加器加载

    2)扩展类的加载器

    如果 classpath 和 JAVA_HOME/jre/lib/ext 下有同名类,加载时会使用拓展类加载器加载。当应用程序类加载器发现拓展类加载器已将该同名类加载过了,则不会再次加载。

    3)双亲委派模式

    双亲委派模式,即调用类加载器ClassLoader 的 loadClass 方法时,查找类的规则。

    loadClass源码

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { 
      synchronized (getClassLoadingLock(name)) { 
        // 1. 检查该类是否已经加载 
        Class<?> c = findLoadedClass(name); 
        if (c == null) { 
          long t0 = System.nanoTime(); 
          try {
            if (parent != null) { 
              // 2. 有上级的话,委派上级 (递归调用)
              loadClass c = parent.loadClass(name, false); 
            } else { 
              // 3. 如果没有上级了(ExtClassLoader),则委派 BootstrapClassLoader(没有就没有了,不会再向上递归)
              c = findBootstrapClassOrNull(name); 
            } 
          } catch (ClassNotFoundException e) {
          }
          
          if (c == null) { 
            long t1 = System.nanoTime(); 
            // 4. 每一层找不到,调用 findClass 方法(每个类加载器自己扩展,重写了的)去加载
            c = findClass(name); 
            // 5. 记录耗时
            sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
            sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 
            sun.misc.PerfCounter.getFindClasses().increment(); 
          } 
        }
        if (resolve) { 
          resolveClass(c); 
        }
        return c; 
      } 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
  • 相关阅读:
    产品设计师根据处理哪些问题来设计外观?
    使用 ABAP 代码制作手机能够扫描的二维码(QRCode)
    云原生爱好者周刊:Prometheus 推出 Agent 模式来适应新的使用场景
    flutter 常用组件:文本、图片和按钮
    算法题整理(蓝桥 & leetcode)(待更新)
    OC和Swift的区别,发送消息和执行方法的区别
    [附源码]SSM计算机毕业设计学校缴费系统JAVA
    游戏数据分析实战 | 学习笔记
    全志V853芯片 如何在Tina V85x平台切换sensor?
    博士论文答辩流程
  • 原文地址:https://blog.csdn.net/qq_54699828/article/details/125900236