java
主页 > 软件编程 > java >

JVM的类加载器和双亲委派模式你了解吗

2022-03-13 | 秩名 | 点击:

类加载器

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

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

名称 加载的类 说明
Bootstrap ClassLoader(启动类加载器) JAVA_HOME/jre/lib 无法直接访问
Extension ClassLoader(拓展类加载器) JAVA_HOME/jre/lib/ext 上级为Bootstrap,显示为null
Application ClassLoader(应用程序类加载器) classpath 上级为Extension
自定义类加载器 自定义 上级为Application

1、启动类加载器

可通过在控制台输入指令,使得类被启动类加器加载,它是用C++写的,看不到源码;其他类加载器是用Java写的,说白了就是一些Java类,比如扩展类加载器、应用类加载器。

1

2

3

4

5

//查询所有被启动类加载器加载的类

URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();

for (URL url : urls) {

    System.out.println(url);

}

1

2

3

4

5

6

7

8

9

//查询到的结果

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/resources.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/rt.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/sunrsasign.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/jsse.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/jce.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/charsets.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/jfr.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/classes

由上可以看出启动类加载的都是jre和jre/lib目录下的核心库,具体路径要看你的jre安装在哪里。

2、拓展类加载器

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

1

2

3

4

URL[] urls = ((URLClassLoader) ClassLoader.getSystemClassLoader().getParent()).getURLs();

for (URL url : urls) {

    System.out.println(url);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/access-bridge-64.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/cldrdata.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/dnsns.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/dns_sd.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/jaccess.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/jfxrt.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/localedata.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/nashorn.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/sunec.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/sunjce_provider.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/sunmscapi.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/sunpkcs11.jar

file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/zipfs.jar

这些类库具体是什么不重要,只需要知道不同的类库可能是被不同的类加载器加载的。

3、应用程序类加载器

1

2

3

4

URL[] urls = ((URLClassLoader) ClassLoader.getSystemClassLoader()).getURLs();

for (URL url : urls) {

    System.out.println(url);

}

1

file:/{项目工程目录}/bin/

这是当前java工程的bin目录,也就是我们自己的Java代码编译成的class文件所在。

4、双亲委派模式

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

loadClass源码

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

protected Class<?> loadClass(String name, boolean resolve)

    throws ClassNotFoundException

{

    synchronized (getClassLoadingLock(name)) {

        // 首先查找该类是否已经被该类加载器加载过了

        Class<?> c = findLoadedClass(name);

        //如果没有被加载过

        if (c == null) {

            long t0 = System.nanoTime();

            try {

                //看是否被它的上级加载器加载过了 Extension的上级是Bootstarp,但它显示为null

                if (parent != null) {

                    c = parent.loadClass(name, false);

                } else {

                    //看是否被启动类加载器加载过

                    c = findBootstrapClassOrNull(name);

                }

            } catch (ClassNotFoundException e) {

                // ClassNotFoundException thrown if class not found

                // from the non-null parent class loader

                //捕获异常,但不做任何处理

            }

            if (c == null) {

                //如果还是没有找到,先让拓展类加载器调用findClass方法去找到该类,如果还是没找到,就抛出异常

                //然后让应用类加载器去找classpath下找该类

                long t1 = System.nanoTime();

                c = findClass(name);

                // 记录时间

                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);

                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);

                sun.misc.PerfCounter.getFindClasses().increment();

            }

        }

        if (resolve) {

            resolveClass(c);

        }

        return c;

    }

}

有一个描述类加载器加载类过程的术语:双亲委派模型。然而这是一个很有误导性的术语,它应该叫做单亲委派模型(Parent-Delegation Model)。但是没有办法,大家都已经这样叫了。所谓双亲委派,这个亲就是指ClassLoader里的全局变量parent,也就是父加载器。

双亲委派的具体过程如下:

在这里插入图片描述

为什么要双亲委派?

答:确保类的全局唯一性。

如果你自己写的一个类与核心类库中的类重名,会发现这个类可以被正常编译,但永远无法被加载运行。因为你写的这个类不会被应用类加载器加载,而是被委托到顶层,被启动类加载器在核心类库中找到了。如果没有双亲委托机制来确保类的全局唯一性,谁都可以编写一个java.lang.Object类放在classpath下,那应用程序就乱套了。

从安全的角度讲,通过双亲委托机制,Java虚拟机总是先从最可信的Java核心API查找类型,可以防止不可信的类假扮被信任的类对系统造成危害。

5、自定义类加载器

如果我们自己去实现一个类加载器,基本上就是继承ClassLoader之后重写findClass方法,且在此方法的最后调包defineClass。

5.1、使用场景

5.2、步骤

1

2

3

4

5

6

7

protected Class<?> findClass(final String name) throws ClassNotFoundException {

    // 1、安全检查

    // 2、根据绝对路径把硬盘上class文件读入内存

    byte[] raw = getBytes(name);

    // 3、将二进制数据转换成class对象

    return defineClass(raw);

}

原文链接:https://blog.csdn.net/weixin_45379261/article/details/123446935
相关文章
最新更新