【JVM类加载类的初始化和类加载器双亲委托机制

2024年 3月 12日 88.9k 0

以此递进,先加载test9调用子类,先初始化父类,类的初始化7中之一。

有两种类型的类加载器

java虚拟机自带的加载器

  • 根类加载器(Bootstrap)
  • 扩展类加载器(Extension)
  • 系统(应用)类加载器(System)(2,3都属于Launcher类的内部类)

用户自定义的类加载器

  • java.lang.ClassLoader的子类。
  • 用户可以定制类的加载方式。
  • 包括自定义类加载器在构造的时候在构造方法中传入了一个父类加载。

类的加载器并不需要等到某个类被“首次主动使用”时再加载它(个人理解 加载不一定初始化,初始化一定加载)。

类的加载

  • jvm规范允许类加载器在预料某个类将要被使用时就预先加载他,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)。
  • 如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。

类被加载后,就进入链接阶段。链接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去。

类的初始化类的初始化步骤

  • 假如这个类还没有被加载和连接,那就先进行加载和连接。
  • 假如类存在直接父类,并且这个父类还没有被初始化,那就先初始化直接父类。
  • 假如类中存在初始化语句,那就依次执行这些初始化语句。

类的初始化时机

主动使用(7种,重要)除了七种情形,其他使用java类的方式都被看做是被动使用,不会导致类的初始化。

初始化时机

jdk1.8在之后首次调用接口的静态方法和default方法也会导致接口初始化。

初始化时机

示例:

public class Test5 {
    public static void main(String[] args) {
          //验证初始化一个类时,并不会先初始化它所实现的接口
//        System.out.println(MyChild5.b);
        //验证初始化一个接口时,并不会初始化它所实现的接口
        System.out.println(MyChild5_1.thread);
    }
}
interface MyParcnt5{

    public static Thread thread = new Thread(){
        {
            System.out.println("MyParcnt5 invoked");
        }
    };
    public static final int a = 5;
}
//interface MyChild5 extends MyParcnt5{
//
//    public static int b = new Random().nextInt(4);
//}
class MyChild5 implements MyParcnt5{
    //此时MyChild5 被加载并没有被初始化  (加载不一定初始化,初始化一定加载)
    public static int b = 6;
}
interface MyParcnt5_1{
    public static Thread thread = new Thread(){
        {
            System.out.println("MyParcnt5_1 invoked");
        }
    };
}
interface MyChild5_1 extends MyParcnt5_1 {
    public static Thread thread = new Thread(){
        {
            System.out.println("MyChild5_1 invoked");
        }
    };
}

类加载器双亲委托机制详解

他们之间存在的是包含关系 不是继承关系树形结构。

public abstract class ClassLoader {

    private static native void registerNatives();
    static {
        registerNatives();
    }

    // The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    private final ClassLoader parent;

ClassLoader中有一个成员变量定义着双亲 因为除了根类加载器每个加载器都继承于ClassLoder 所以每个ClassLoaderd都存在着对应的双亲 所以他们之间存在的是包含关系 不是继承关系树形结构。

加载过程

在双亲委托机制中,各个加载器按照父子关系形成了树形结构,除了根类加载器之外,其余的类加载器都有且只有一个父加载器。

加载过程

每个类都需要类加载器去加载,如果有父类,先让父类去加载,如此向上追溯,直到根类加载器,然后根类加载器尝试去加载,加载成功贼结束,加载失败,又往下,一层层的尝试去加载,最终如果都没有加载成功则报错。

加载方式

回顾之前学的知识点。

public class Test7 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class clazz = Class.forName("java.lang.String");
        System.out.println(clazz.getClassLoader());//获取类加载器,如果是Bootstrap ClassLoader 根加载器加载可能会返回null

        Class clazs = Class.forName("com.example.demo.com.jvm.C");
        System.out.println(clazs.getClassLoader());//sun.misc.Launcher$AppClassLoader@18b4aac2 基本上工程编写的类都是由系统(应用)加载器AppClassLoader加载的
    }
}

class C {

}

打印结果:

null
sun.misc.Launcher$AppClassLoader@18b4aac2
class FinalTest {
    public static final int x = 3;
//    public static final int x = new Random().nextInt(3);

    static {
        System.out.println("FinalTest static block");
    }
}

public class Test8 {

    public static void main(String[] args) {
        System.out.println(FinalTest.x);
    }
}
/*
    public static final int x = 3; 时 此时在编译阶段 常量x 会被放进Test8的常量池 直接拿来使用 		    FinalTest 并不会被初始化

    3
 */
/*
    public static final int x = new Random().nextInt(3);时 new Random().nextInt(3)生成随机数实在运行时生成的 所以 Test8和FinalTest  有联系 此时FinalTest类会被加载

    FinalTest static block
    1
*/
class Parent {
    static int a = 3;
    static {
        System.out.println("Parent static block");
    }
}
class Child extends Parent {
    static int b = 4;
    static {
        System.out.println("Child static block");
    }
}
public class Test9 {
    static {
        System.out.println("Test9 static block");
    }
    public static void main(String[] args) {
        System.out.println(Child.b);
    }
}

以此递进 先加载test9 调用子类 先初始化父类 类的初始化7种之一。

/*
以此递进 先加载test9 调用子类 先初始化父类 类的初始化7中之一
 Test9 static block
 Parent static block
 Child static block
 4
 */
class Parent1 {
    static int a = 3;
    static {
        System.out.println("Parent static block");
    }

}
class Child1 extends Parent1 {
    static int b = 4;
    static {
        System.out.println("Child static block");
    }
}
public class Test10 {
    static {
        System.out.println("Test10 static block");
    }
    public static void main(String[] args) {
        Parent1 parent1;
        System.out.println("===========");
        parent1 = new Parent1();
        System.out.println("===========");
        System.out.println(parent1.a);
        System.out.println("===========");
        System.out.println(Child1.b);
    }
}

类只会首次加载才会初始化。

/*
类只会首次加载才会初始化
Test10 static block
===========
Parent static block
===========
3
===========
Child static block
4
 */
class Parent3 {
    static int a = 3;
    static {
        System.out.println("Parent3 static block");
    }
    static void doSomething() {
        System.out.println("do doSomething");
    }
}
class Child3 extends Parent3 {

    static {
        System.out.println("Child3 static block");
    }
}
public class Test11 {
    public static void main(String[] args) {
        System.out.println(Child3.a); //类名.父类静态变量和静态方法 表示对父类的主动使用 此时子类并不初始化
        System.out.println("---------------");
        Child3.doSomething();
    }
}

类名.父类静态变量和静态方法 表示对父类的主动使用 此时子类并不初始化。

//调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化

public class Test12 {

    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader loder = ClassLoader.getSystemClassLoader(); //获取系统加载器

        Class aClass = loder.loadClass("com.example.demo.com.jvm.CL");//加载对应类 除却7种都是被动使用 不会初始化

        System.out.println(aClass);

        System.out.println("-----------------");

        Class aClass1 = Class.forName("com.example.demo.com.jvm.CL");//7种之一
        System.out.println(aClass1);

    }
}

只有反射初始化CL了 调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。

/*
只有反射初始化CL了 调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化

class com.example.demo.com.jvm.CL
-----------------
Class cl
class com.example.demo.com.jvm.CL
 */

不同的类加载器作用与动作分析

隐式加载: 程序在运行过程中碰到通过new等方式生成对象时,隐式调用类装载器加载对应的类到jvm中。

显式装载: 通过class.forname()等方法,显示加载需要的类。

类加载的动态体现

  • 一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现。
  • public static ClassLoader getSystemClassLoader();获取系统类加载器。

返回用于委派的系统类加载器getParent() 返回父类加载器进行委派。

public class Test13 {
    public static void main(String[] args) {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();//获取系统类加载

        System.out.println(classLoader);

        while (null != classLoader) {
            classLoader = classLoader.getParent(); //获取父加载器 因为使用根加载器时 返回值是用null来表示 所以循环结束
            System.out.println(classLoader);
        }
    }
}
/*
jdk自带 的三个类加载器
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1eb44e46
null 并没有继承classLoader
*/
public class Test14 {
    public static void main(String[] args) throws IOException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();//获取当前线程的上下文类加载器 通常是获取那个用于启动应用的类加载器

        String res = "com/example/demo/com/jvm.Test13.class";

        Enumeration resource = classLoader.getResources(res);//获取 类的资源
        while (resource.hasMoreElements()) {
            URL url = resource.nextElement();
            System.out.println(url);
        }

        System.out.println("--------获取类加载器的有对应的的类-----------");

        Class test14Class = Test14.class;
        System.out.println(test14Class.getClassLoader()); //sun.misc.Launcher$AppClassLoader@18b4aac2

    }
}

获取类加载器的几种方法。

获取类加载的几种方式

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论