类加载器-双亲委派机制

JAVA学习网 2018-09-05 00:17:03
  • 类与类加载器的关系
对于任何一个类而言,只有通过类加载器和类本身才能够在JVM中确定唯一性。每一个内加载器都有其唯一的空间,不同的类加载器加载同一个类,这两个内存中的类是不相等的。通俗的说:比较两个类是否相等,只能在同一个类加载器的前提下进行比较,否则加载器不同,类就一定不相等。代码如下:
public class ClassLoaderTest {
    public static void main(String[] args) throws Exception{
        ClassLoader classLoader1 = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException { //1.自定义的类加载器
                String className = name.substring(name.lastIndexOf(".")+1)+".class";
                InputStream inputStream = getClass().getResourceAsStream(className);
                if (inputStream == null){
                    return super.loadClass(name);
                }else{
                    try {
                        byte[] bytes = new byte[inputStream.available()];
                        inputStream.read(bytes);
                        return defineClass(name,bytes,0,bytes.length);
                    } catch (IOException e) {
                        throw new ClassNotFoundException(name);
                    }
                }
            }
        };
        Object object = classLoader1.loadClass("orga.ClassLoaderTest").newInstance();
        System.out.println(object.getClass());
        System.out.println(object instanceof  orga.ClassLoaderTest);//2 系统应用程序类加载器
    }
}

 

运行结果如下:
class orga.ClassLoaderTest
false
解释:1.是我们编写的自定义类加载器,2 “orga.ClassLoaderTest”在运行的时候是由系统应用程序类加载器加载。因此当一个类被不同的类加载器加载的时候,在内存中形成的是两个独立的类;
jvm中主要有三种类加载器:
1.启动加载器 : 用来加载lib目录下的类
2.拓展加载器 : 用来加载我们引入的类(lib\ext下的包)
3.应用程序加载器 : 加载我们编写的类
并且他们都只能加载自己路径下,命名规则正确的类。
 
  • 双亲委派模型

由于不同的类加载器加载相同的类会导致内存中存在重复的类,这样使java最基础的行为无法保证,因此需要一种方法来保证类的唯一性。

  图中展示的这种类加载器之间的层级关系,被称为双亲委派模型,另外他们之间的关系不是继承,而是用组合关系来复用父加载器的部分代码。主要原理是  模型中除启动类加载器之外的类加载器都要有自己的父类加载器。其工作过程是:一个类加载器收到类加载请求,他不会马上去加载这个类,而是将这个请求转给自己的父加载器,每一层都如此,直到顶层的启动类加载器,当父类加载器无法完成这个类的加载(在他的搜索范围内,找不到该类),子加载器才开始尝试加载这个类。这样就可以保证,一个类只能被一个类加载器加载,保证了该类在内存中的唯一性。其实现代码在java.lang.ClassLoader的loadClass方法中,如下图:

 
  • 线程上下文类加载器
  并非所有的类加载机制都遵循这个模型,换言之,这个模型是被破坏过的。双亲委派很好的解决了各个类加载器的基础类的同一问题,基础类是总是被用户代码所调用的API,但是基础类要调用用户的代码的时候,双亲委派就出现了缺陷。比如JNDI服务,属于rt.jar,他需要调用应用程序的代码来实现资源管理,但是启动类加载器并不能识别应用程序代码。因此出现了线程上下文类加载器。这个类加载器由Thread类的setContextClassLoaser()方法进行设置,如果线程未创建,他将会从主线程中继承一个。JNDI可以使用线程上下文加载器来加载所需要的SPI代码,也就是父类加载器去请求子类加载器加载Class。Java中所有涉及SPI加载的基本上都采用这个方法。
阅读(810) 评论(0)