Java高级——类加载器和双亲委派模型

类加载器和双亲委派模型

  • 简述
  • 类加载器
  • 双亲委派模型
  • 破坏双亲委派模型
    • 情况一
    • 情况二
    • 情况三

简述

类通过类加载器加载,一个类由加载它的类加载器和其本身决定其在JVM中的唯一性,每一个类加载器,都拥有一个独立的类名称空间

public class Test {

    public static void main(String[] args) throws Exception{
        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream(fileName);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    e.printStackTrace();
                    throw new ClassNotFoundException(name);
                }
            }
        };
        Object obj = myLoader.loadClass("Test").newInstance();
        System.out.println(obj.getClass());
        System.out.println(obj instanceof Test);
    }
}

如上,自定义了一个类加载器去加载Test,instanceof 输出false

class Test
false

类加载器

从JVM角度看存在

  • 启动类加载器(Bootstrap ClassLoader):由C++实现,是JVM的一部分,java中用null代替
  • 其他类加载器:由Java实现,独立于JVM,继承自ClassLoader

从开发者角度看存在:

  • 启动类加载器(Bootstrap Class Loader):加载\lib或-Xbootclasspath指定路径中的类库
  • 扩展类加载器(Extension Class Loader):在sun.misc.Launcher$ExtClassLoader,加载\lib\ext或java.ext.dirs指定路径中的类库
  • 应用程序类加载器(Application Class Loader):在sun.misc.Launcher$AppClassLoader,通过ClassLoader.getSystemClassLoader()返回,加载用户类路径(ClassPath)上的类库

Java高级——类加载器和双亲委派模型_第1张图片

除了启动类加载器外,其余的类加载器都有自己的父类加载器,通过组合而非继承关系来复用父加载器的代码

双亲委派模型

  • 任意类加载器收到了类加载的请求
  • 把请求委派给父类加载器去完成(直到传给启动类加载器)
  • 当父类加载器无法完成加载(其搜索范围未找到所需的类)
  • 子加载器才会尝试去加载

双亲委派模型让类随着它的类加载器形成优先级的层次关系,如对于Object,无论哪个类加载器加载,最终都委派给启动类加载器,从而保证了唯一性

protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
    // 首先,检查请求的类是否已经被加载过了
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) {
            // 如果父类加载器抛出ClassNotFoundException
            // 说明父类加载器无法完成加载请求
        }
        if (c == null) {
            // 在父类加载器无法加载时
            // 再调用本身的findClass方法来进行类加载
            c = findClass(name);
        }
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
}

上面为java.lang.ClassLoader的loadClass()方法中双亲委派模型的代码实现

破坏双亲委派模型

情况一

双亲委派模型在JDK1.2推出,在此之前的类加载器都是通过复写loadClass()方法加载类

为了兼容,无法再以技术手段避免loadClass()被子类复写,即可以通过复写loadClass()方法双亲委派模型

为了避免破坏双亲委派模型,JDK1.2新增protect的findClass()方法,让用户尽可能重写findClass()而不是loadClass(),这样既不影响用户自定义加载类,又可保证新写的类加载器符合双亲委派模型

情况二

根据双亲委派模型,越基础的类由越上层的加载器加载,基础类型是作为被用户代码继承、调用的API存在

而基础类型要调用回用户的代码(加载资源类),则需通过线程上下文类加载器(Thread Context ClassLoader)

  • 其通过Thread的setContextClassLoader()方法设置
  • 若创建线程时还未设置,将会从父线程中继承一个
  • 若在应用程序的全局范围内都未设置,则默认使用Application Class Loader

这是一种由父类加载器去请求子类加载器完成类加载的行为,打通了双亲委派模型的层次结构来逆向使用类加载器,典型应用有JNDI、JDBC等

情况三

IBM推出的OSGi为了实现模块热部署,每个程序模块(称为Bundle)都有自身的类加载器,当需要更换Bundle时连同类加载器一起换掉以实现代码的热替换

在OSGi环境下,类加载器不再是双亲委派模型的树状结构,而是网状结构,当收到类加载请求时,按照下面的顺序进行类搜索

  • 将以java.*开头的类,委派给父类加载器加载
  • 否则,将委派列表名单内的类,委派给父类加载器加载
  • 否则,将Import列表中的类,委派给Export这个类的Bundle的类加载器加载
  • 否则,查找当前Bundle的ClassPath,使用自己的类加载器加载
  • 否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载
  • 否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载
  • 否则,类查找失败

上面只有前两点仍符合双亲委派模型,其余的类查找都是在平级的类加载器中进行的

你可能感兴趣的:(#,Java高级,java,jvm,开发语言)