双亲委派模型原理
1-类加载器收到类加载的请求;
2-把这个请求委托给父加载器去完成,一直向上委托,直到启动类加载器;
3-启动器加载器检查能不能加载(使用findClass()方法),能就加载(结束);否则,抛出异常,通知子加载器进行加载。
4-重复步骤三;
其实在Java的日常应用程序开发中,类的加载几乎是由BootStrap类加载器,Extension类加载器,Application类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,需要注意的是,Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式即把请求交由父类处理。
为什么要使用双亲委派模型呢?
其实这个时候就有一道非常有意思的面试题了大家看一下
能不能自己写个类叫java.lang.System?
其实这也是阿里面试的一道很经典的面试题,我们来做个分析。
之前看过一个文章说的就是说这个通常不可以,但可以采取另类方法达到这个需求。所谓的另类方法指自己写个类加载器来加载java.lang.System达到目的。
但是实际上却是是不能的,为了输出,我们做个最简单的测试用Math类来进行测试,效果和原理类似,这个例子是我之前在看一个文章的时候随手记录下拉的,当时运行了一下,确实很有道理,大家可以参考一下。
包结构如下:
代码
package com.tq; import java.io.InputStream; public class MyClassLoader extends ClassLoader { public MyClassLoader() { super(null); } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try{ String className = null; if(name.startsWith("java.lang")){ className = "/" + name.replace('.', '/') + ".class"; }else{ className = name.substring(name.lastIndexOf('.') + 1) + ".class"; } System.out.println(className); InputStream is = getClass().getResourceAsStream(className); System.out.println(is); if(is == null) return super.loadClass(name); byte[] b = new byte[is.available()]; is.read(b); return defineClass(name, b, 0, b.length); }catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } }
package java.lang; public final class Math { public static void main(String[] args) { System.out.println("hello world"); } }
package java.lang; public class MyMath { public static void main(String[] args) { System.out.println("hello world"); } }
我们在运行Math里面的输出方法的时候就会出现问题,
提示Math类没有main方法,这就是我们在之前预测根据双亲委托原则,Math类首先由启动类加载器去尝试加载,它找到rt.jar中的java.lang.Math类并加载进内存(并不会加载我们自定义的Math类),然后执行main方法时,发现不存在该方法,所以报方法不存在错误。也就是说,默认情况下JVM不会加载我们自定义的Math类。
java.lang.SecurityException: Prohibited package name: java.lang 他不允许我们使用java.lang的包名
我们继续运行下ClassLoaderTest类
我们看一下ClassLoader的源码
private ProtectionDomain preDefineClass(String name, ProtectionDomain pd) { if (!checkName(name)) throw new NoClassDefFoundError("IllegalName: " + name); // Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias // relies on the fact that spoofing is impossible if a class has a name // of the form "java.*" if ((name != null) && name.startsWith("java.")) { throw new SecurityException ("Prohibited package name: " + name.substring(0, name.lastIndexOf('.'))); } if (pd == null) { pd = defaultDomain; } if (name != null) checkCerts(name, pd.getCodeSource()); return pd; }
通过代码实例及源码分析可以看到,对于自定义的类加载器,强行用defineClass()方法去加载一个以"java."开头的类也是会抛出异常的。
总结
1. 双亲委派模型最大的好处就是让Java类同其类加载器一起具备了一种带优先级的层次关系。这句话可能不好理解,我们举个例子。比如我们要加载java.lang.Object类,无论我们用哪个类加载器去加载Object类,这个加载请求最终都会委托给Bootstrap ClassLoader,这样就保证了所有加载器加载的Object类都是同一个类。如果没有双亲委派模型,那就乱了套了,完全可能搞出多个不同的Object类。
2. 自上而下每个类加载器都会尽力加载.
3. 不能自己写以"java."开头的类,其要么不能加载进内存,要么即使你用自定义的类加载器去强行加载,也会收到一个SecurityException。
我是懿,一个正在被打击还在努力前进的码农。欢迎大家关注我们的公众号,加入我们的知识星球,我们在知识星球中等着你的加入。