sponsored links

双亲委派机制简述

一直都在听双亲委派 双亲委派 到底是什么东西,在网上看了几篇比较好的博客  自己发个文来记录一下 全当笔记

1.双亲委派发生在类加载阶段。

其作用也就是通过类加载器将class文件加载到虚拟机中以备调用。

在java中自带三个类加载器包括

1.Bootstrap Classloader  用c语言编写是最顶层的加载器。在java代码中如果尝试获取 会返回null。原因很简单,因为是c++语言编写无法用java对象来描述

可通过System.out.println(System.getProperty("sun.boot.class.path"));获取Bootstrap规定的加载路径

会得到:

C:\Program Files\Java\jre1.8.0_91\lib\resources.jar;

C:\Program Files\Java\jre1.8.0_91\lib\rt.jar;

C:\Program Files\Java\jre1.8.0_91\lib\sunrsasign.jar;

C:\Program Files\Java\jre1.8.0_91\lib\jsse.jar;

C:\Program Files\Java\jre1.8.0_91\lib\jce.jar;

C:\Program Files\Java\jre1.8.0_91\lib\charsets.jar;

C:\Program Files\Java\jre1.8.0_91\lib\jfr.jar;

C:\Program Files\Java\jre1.8.0_91\classes

即bootstrap加载器会加载这些路径中的类

2.Extention Classloader 扩展的类加载器

加载路径可以通过下面的语句得到:

System.out.println(System.getProperty("java.ext.dirs"));

得到:

C:\Program Files\Java\jre1.8.0_91\lib\ext;C:\Windows\Sun\Java\lib\ext

3.Appclass Classloader 负责加载classpath中的所有类

加载路径可以通过下面的语句得到:

System.out.println(System.getProperty("java.class.path"));

得到:

\workspace\ClassLoaderDemo\bin

至于为什么上面的语句会得到 都在每一个类的源码中 第一步就是读取这几个定义好的加载路径

至于这三个类加载器,是有层级关系的 

比如eclipse中在src下的任意一个类运行如下代码(假设类名为TestClassLoader)

如果通过 System.out.println(TestClassLoader.getClassLoader().toString() )

会得到

sun.misc.Launcher$AppClassLoader@4554617c

这证明了 在src目录也就是默认的classpath目录下加载类的加载器默认为 AppClassLoader

如果想得到这个类加载器的父类加载器也就是上面所说的层级关系

可以通过 System.out.println(TestClassLoader.getClassLoader().getParent().toString() )

会得到

sun.misc.Launcher$ExtClassLoader@677327b6

结果说明 AppClassLoader的父加载器是ExtClassLoader.但是如果想得到extClassLoader的父类加载器 得到的会是nullpointerexception 这证明在toString的时候是null。也就是说 extClassLoader的父类加载器是null也就是上面提到的顶级加载器

BootstrapClassloader

上源码:

static class ExtClassLoader extends URLClassLoader {}

static class AppClassLoader extends URLClassLoader {}

以上代码可以看出 这两个加载器 都继承了同一个父类,这也就是前面提到为什么是层级关系,虽然每一个加载器都有一个父类加载器,但父类加载器 不是这个加载器的父类,这是两个不同的概念 需要区分。

 

做了如此多铺垫 终于到了双亲委派机制的解读

1.极其重要的一个方法 loadClass

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

        throws ClassNotFoundException

    {

        synchronized (getClassLoadingLock(name)) {

            // First, check if the class has already been loaded

            //findLoadedClass() 是去内存中查找是否加载过这个名字为 name的类,上面的注释是源码中的注释,也是在说 检查这个类是否已经被加载了

            Class<?> c = findLoadedClass(name);

            if (c == null) {

                long t0 = System.nanoTime();

                try {

                      //parent为classload类中的属性,表示它的父加载器,如果父加载器不为空则调用父类的loadClass方法

                     //可以想到如果当前类加载器没有在内存中找到该类,那么它的父类加载器就会去内存中寻找是否加载了该类

                      if (parent != null) {

                        c = parent.loadClass(name, false);

                    } else {

                        //如果父加载器为空,那么调用BootstrapClassLoader去寻找该类,这也证明了BootstrapClassLoader在java中是

                       //以null的形式存在的

                        c = findBootstrapClassOrNull(name);

                    }

                } catch (ClassNotFoundException e) {

                    // ClassNotFoundException thrown if class not found

                    // from the non-null parent class loader

                }

 

                if (c == null) {

                   //如果还是没有找到,那么调用findClass去查找 也就是从上面提到的路径中找

                    // If still not found, then invoke findClass in order

                    // to find the class.

                    long t1 = System.nanoTime();

                    c = findClass(name);

 

                    // this is the defining class loader; record the stats

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

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

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

                }

            }

            if (resolve) {

                resolveClass(c);

            }

            return c;

        }

    }

 

看完代码,整个过程就清晰很多 PS:这里不要纠结每一个方法都在干什么。我的水平现在还达不到理解这些方法的阶段,因为追踪这些方法 ,最后看到的都是native修饰的本地方法,所以方法的功能我暂时也只能通过注释来判断。

我把上述过程总结一下:

以上面提到的任意一个类TestClassLoader.class为例

首先会调用自身的findLoadedClass()去内存中寻找这个类,在这个例子中就是通过AppClassLoader如果找不到

则会调用其父类加载器 即ExtClassLoader同样去内存中寻找,如果找不到则会再通过父类加载器即BootStrap来寻找,

如果都没有找到则会一层一层返回,即BootStrap通过它自身定义的加载路径加载,如果加载不到则会通过ExtClassLoader

根据自身定义的加载路径加载,如果加载不到则会通过AppClassLoader加载。如果都加载不到,恭喜 ClassNotFund异常等着你。

这就是双亲委派的大致过程,可以这么理解 ,在加载一个类的时候会先自己看一眼 内存里没有 就拜托父类加载器来做,它等着,如果父类加载器还是没找到,就会拜托给父类的父类加载器,直到老祖宗BootStrap加载器也宣告没有找到,但是内存中找不到规找不到,老祖宗还是会很努力的解决问题就会尝试在自己的管辖区域去找,如果找不到 只能告诉儿子,我找也没找到,加载也加载不到,剩下的只能靠你自己了,然后它的儿子就会重复这样的过程,直到最后找到为止。

 

2.双亲委派为什么要这么做?(一点浅显的理解)

例如类java.lang.Object,它存放在rt.jar中,无论哪个类加载器要加载这个类。

如果这时的你很皮(皮有时候是件好事) 你自己写了一个高仿java.lang.Object 放在了src目录中,

你很自豪 ,宣称好了 我把java老祖宗类改了,但事实上是这样的,在你加载这个类的时候,因为名字和rt.jar中的相同,

假设这两个Object都还没有加载,那么首先自身的加载器 AppClassLoader会去内存中找,找不到,直到bootstrap也在内存中找不到,那这时bootstrap会加载rt.jar中的Object,至此加载结束,你自己的高仿是加载不到的。

假设内存中原有的Object已经加载,那么更简单,你兴致冲冲的写完了高仿准备装逼的时候,bootstrap 或者AppClassLoader 已经在内存中找到了(至于哪个加载器寻找哪一个区域的内存空间我不是很清楚 所以也不敢妄下定论)那你的高仿又失去了表现机会,这样的加载模式,保证了java自身类的安全。

 

以上作为学习心得,水平有限,如有错误,请笑掉大牙并指正,感激不尽。

引用

这篇文章参考并部分摘自

https://blog.csdn.net/briblue/article/details/54973413

通过这个大神学到了很多,也深表感谢,如果侵权,我马上删除。

Tags: