ClassLoader 类加载器模型

共 6499字,需浏览 13分钟

 ·

2021-10-26 21:22

一、 类加载器

ClassLoader即常说的类加载器,其功能是用于从Class文件加载所需的类,主要场景用于热部署、代码热替换等场景。

系统提供3种类加载器:Bootstrap ClassLoader、Extension ClassLoader、Application ClassLoader

1.1 Bootstrap ClassLoader

启动类加载器,一般由C++实现,是虚拟机的一部分。该类加载器主要职责是将JAVA_HOME路径下的\lib目录中能被虚拟机识别的类库(比如rt.jar)加载到虚拟机内存中。Java程序无法直接引用该类加载器

1.2 Extension ClassLoader

扩展类加载器,由Java实现,独立于虚拟机的外部。该类加载器主要职责将JAVA_HOME路径下的\lib\ext目录中的所有类库,开发者可直接使用扩展类加载器。该加载器是由sun.misc.Launcher$ExtClassLoader实现。

1.3 Application ClassLoader

应用程序类加载器,该加载器是由sun.misc.Launcher$AppClassLoader实现,该类加载器负责加载用户类路径上所指定的类库。开发者可通过ClassLoader.getSystemClassLoader()方法直接获取,故又称为系统类加载器。当应用程序没有自定义类加载器时,默认采用该类加载器。

ClassLoader.java

 1public static ClassLoader getSystemClassLoader() {
2    initSystemClassLoader(); //初始化系统类加载器 【见下文】
3    if (scl == null) {
4        return null;
5    }
6    SecurityManager sm = System.getSecurityManager();
7    if (sm != null) {
8        ClassLoader ccl = getCallerClassLoader();
9        if (ccl != null && ccl != scl && !scl.isAncestor(ccl)) {
10            sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
11        }
12    }
13    return scl;
14}

系统类加载器初始化

 1private static synchronized void initSystemClassLoader() {
2    if (!sclSet) {
3        if (scl != null)
4            throw new IllegalStateException("recursive invocation");
5        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
6        if (l != null) {
7            Throwable oops = null;
8            scl = l.getClassLoader();
9            try {
10                scl = AccessController.doPrivileged(
11                    new SystemClassLoaderAction(scl));
12            } catch (PrivilegedActionException pae) {
13                oops = pae.getCause();
14                if (oops instanceof InvocationTargetException) {
15                    oops = oops.getCause();
16                }
17            }
18            if (oops != null) {
19                if (oops instanceof Error) {
20                    throw (Error) oops;
21                } else {
22                    throw new Error(oops);
23                }
24            }
25        }
26        sclSet = true;
27    }
28}

二、双亲委派模型

ClassLoader的双亲委派模型中,各个ClassLoader之间的关系是通过组合关系来复用父加载器。当一个ClassLoader收到来自类加载的请求,首先把该请求委派该父类ClassLoader处理,当父类ClassLoader无法处理时,才由当前类ClassLoader来处理。对于每个ClassLoader这个方式,也就是父类的优先于子类处理类加载的请求,那么也就是说任何一个请求第一次处理的便是最顶层的Bootstrap ClassLoader(启动类加载器)。

类加载器的层级查找顺序依次为:启动类加载器,扩展类加载器,系统类加载器。系统类加载器是默认的应用程序类加载器。

这样的好处是不同层次的类加载器具有不同优先级,比如所有Java对象的超级父类java.lang.Object,位于rt.jar,无论哪个类加载器加载该类,最终都是由启动类加载器进行加载,保证安全。即使用户自己编写一个java.lang.Object类并放入程序中,虽能正常编译,但不会被加载运行,保证不会出现混乱。那么有人会继续追问,如果自己再自定义一个类加载器来加载自己定义的java.lang.Object类呢? 这样做也是不会成功的,虚拟机将会抛出一异常。

 1protected Class loadClass(String name, boolean resolve)
2    throws ClassNotFoundException
3{
4    synchronized (getClassLoadingLock(name)) {
5        //检查该类是否已经加载过
6        Class c = findLoadedClass(name);
7        if (c == null) {
8            //如果该类没有加载,则进入该分支
9            long t0 = System.nanoTime();
10            try {
11                if (parent != null) {
12                    //当父类的加载器不为空,则通过父类的loadClass来加载该类
13                    c = parent.loadClass(name, false);
14                } else {
15                    //当父类的加载器为空,则调用启动类加载器来加载该类
16                    c = findBootstrapClassOrNull(name);
17                }
18            } catch (ClassNotFoundException e) {
19                //非空父类的类加载器无法找到相应的类,则抛出异常
20            }
21
22            if (c == null) {
23                //当父类加载器无法加载时,则调用findClass方法来加载该类
24                long t1 = System.nanoTime();
25                c = findClass(name); //用户可通过覆写该方法,来自定义类加载器
26
27                //用于统计类加载器相关的信息
28                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
29                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
30                sun.misc.PerfCounter.getFindClasses().increment();
31            }
32        }
33        if (resolve) {
34            //对类进行link操作
35            resolveClass(c);
36        }
37        return c;
38    }
39}

当开发者需要自定义类加载器时,可通过覆写loadClass()方法或者findClass()。

三、自定义类加载器

每一个ClassLoader都拥有自己独立的类名称空间,类是由ClassLoader将其加载到Java虚拟机中,故类是由加载它的ClassLoader和该类本身一起确定其在Java 运行时环境的唯一性。故只有同一个ClassLoader加载的同一个类,才能算是Java 运行时环境中的相同的两个类。哪怕是来自同一个Class文件,即使被同一个虚拟机加载的两个类,只要ClassLoader不同,那么也属于不同的类。对于equals()、isinstanceof()等方法来判断对象的相等或所属关系都是需要基于同一个ClassLoader。

自定义类加载器示例

 1package com.yuanhh.classloader;
2
3import java.io.IOException;
4import java.io.InputStream;
5
6public class ClassLoadDemo{
7
8    public static void main(String[] args) throws Exception {
9
10        ClassLoader clazzLoader = new ClassLoader() {
11            @Override
12            public Class loadClass(String name) throws ClassNotFoundException {
13                try {
14                    String clazzName = name.substring(name.lastIndexOf(".") + 1) + ".class";
15
16                    InputStream is = getClass().getResourceAsStream(clazzName);
17                    if (is == null) {
18                        return super.loadClass(name);
19                    }
20                    byte[] b = new byte[is.available()];
21                    is.read(b);
22                    return defineClass(name, b, 0, b.length);
23                } catch (IOException e) {
24                    throw new ClassNotFoundException(name);
25                }
26            }
27        };
28
29        String currentClass = "com.yuanhh.classloader.ClassLoadDemo";
30        Class clazz = clazzLoader.loadClass(currentClass);
31        Object obj = clazz.newInstance();
32
33        System.out.println(obj.getClass());
34        System.out.println(obj instanceof com.yuanhh.classloader.ClassLoadDemo);
35    }
36}

上面代码的输出结果

1class com.yuanhh.classloader.ClassLoadDemo
2false

输出结果的第一行,可以看出这个对象的确是com.yuanhh.classloader.ClassLoadDemo实例化的对象;但第二句是false,这是由于代码中的obj是由用户自定义的类加载器clazzLoader来加载的,可通过obj.getClass().getClassLoader()获取该对象的类加载器为com.yuanhh.classloader.ClassLoadDemo$xxx,而虚拟机本身会由系统类加载器加载的类ClassLoadDemo,可通过ClassLoadDemo.class.getClassLoader()得其类加载器为sun.misc.Launcher$AppClassLoader@XXX。所以可得出结论:即使都是来自同一个Class文件,加载器不同,仍然是两个不同的类,所以返回值是false。

通过Class.forName()方法加载的类,采用的是系统类加载器。

四、 经典应用场景

  • Tomcat,类加载器架构,自己定义了多个类加载器,

    • 保证了同一个服务器的两个Web应用程序的Java类库隔离;

    • 保证了同一个服务器的两个Web应用程序的Java类库又可以相互共享;比如多个Spring组织的应用程序不能共享,会造成资源浪费;

    • 保证了服务器尽可能保证自身的安全不受不受部署Web应用程序影响;

    • 支持JSP应用的服务器,大多需要支持热替换(HotSwap)功能。

  • OSGi(Open Service GateWay Initiative),是基于Java语言的动态模块化规范。已成为Java世界的“事实上”的模块化标准,最为熟悉的案例的Eclipse IDE。

source: //yuanfentiank789.github.io/2016/01/24/java-classloader

喜欢,在看

浏览 25
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报