类加载器之双亲委派模型

jvm类加载过程:转载、连接、初始化。       具体看这个博客           类加载的详细过程看这个博客

 双亲委派模型:


  • 启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
  • 扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
  • 应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。

当一个类加载器收到类加载任务,会先交给其父类加载器去完成,因此最终加载任务都会传递到顶层的启动类加载器,只有当父类加载器无法完成加载任务时,才会尝试执行加载任务。

采用双亲委派的一个好处是比如加载位于rt.jar包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个Object对象。

双亲委派模型为什么安全:比如自己写了一个String类,AppClassLoader加载这个类的时候会先询问父类加载器是否加载过了,Extension ClassLoader加载器只能加载JAVA_HOME\jre\lib中的class类,就加载了api里面string,不会加载自己写的,所以Application ClassLoader也就不在加载自己写的string类。保证了安全性。    

我做了下测试,类加载是根据包完全限定名加载的,如果有个自定义java.lang.Integer类与api里面一样,那么就会报错,并不是说只加载api里面的不加载自定义的了。(如果自定义了类加载器,双亲委派模型也会避免这个类被加载)

看到一个解释比较靠谱,暂时按照这个理解:类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent 找不到,那么才由自己依照自己的搜索路径搜索类

下面是个demo,自定义Integer类,包名为java.lang

package java.lang;

public class Integer {
	public Integer() {
		System.out.println("输出自定义Interger");
	}
}

测试

package java.lang;

public class MaIntegerDemo {
public static void main(String[] args) {
	Integer integer = new Integer();
}
}


报错 prohibited禁止这个包名

Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
	at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)

看下报错源代码

 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;
    }

为什么要自定义加载器:

1)加密:java代码可以轻易的被反编译,如果你需要对你的代码进行加密以防止反编译,可以先将编译后的代码用加密算法加密,类加密后就不能再使用java自带的类加载器了,这时候就需要自定义类加载器.

2)从非标准的来源加载代码:字节码是放在数据库,甚至是云端,就可以自定义类加载器,从指定来源加载类.

自定义类加载器步骤:(1)继承ClassLoader    (2)重写findClass()方法   (3)调用defineClass()方法

1、如果不想打破双亲委派模型,那么只需要重写findClass方法即可

2、如果想打破双亲委派模型,那么就重写整个loadClass方法

具体demo看这个文章:https://www.cnblogs.com/gdpuzxs/p/7044963.html

猜你喜欢

转载自blog.csdn.net/Mint6/article/details/80864788