`

为什么要有双亲委派模型

    博客分类:
  • JVM
jvm 
阅读更多
来阿里玩Java也有一个多月了,一直对Java虚拟机比较感兴趣,而ClassLoader是整个class载入过程中很重要的组件。而classloader有个双亲委派模型,师兄说这个模型不能破坏,于是打赌一试。

相信如果问:为什么要双亲委派,可能有人可以侃侃而谈,但是说到为什么要这么分层,为什么要分三层,如何绕过双亲委派模型。。。

这就不是那么容易了,这个时候就需要一些专研了。

二、classloader的作用
这个问题我问了师兄:加载+连接的所有过程,但是深入理解Java虚拟机说的不太一样(所以有待考证)



请原谅我贴图,但下面两张图字字珠玑(p228):





classloader虽然只用于实现类的加载动作,但在Java程序中作用却远远不限于类加载阶段,也就是后面说的可以决定类。

三、为什么要3个classloader
我个人认为有两个原因,当然可能不止。

1、是为了安全
http://stackoverflow.com/questions/28011224/what-is-the-reason-for-having-3-class-loaders-in-java

The reason for having the three basic class loaders (Bootstrap, extension, system) is mostly security.

A key concept is the fact that the JVM will not grant package access (the access that methods and fields have if you didn’t specifically mention private, public or protected) unless the class that asks for this access comes from the same class loader that loaded the class it wishes to access.

So, suppose a user calls his class java.lang.MyClass. Theoretically, it could get package access to all the fields and methods in the java.lang package and change the way they work. The language itself doesn’t prevent this. But the JVM will block this, because all the real java.lang classes were loaded by bootstrap class loader. Not the same loader = no access.

There are other security features built into the class loaders that make it hard to do certain types of hacking.

So why three class loaders? Because they represent three levels of trust. The classes that are most trusted are the core API classes. Next are installed extensions, and then classes that appear in the classpath, which means they are local to your machine.

For a more extended explanation, refer to Bill Venners’s “Inside the Java Virtual Machine”.

2、另外,这个帖子没有提到的应该是隔离。
java的所有类都是由classloader加载的,不同classloader之间加载的类彼此是不可见的。tomcat加载了log4j,容器里servlet也加载了log4j,servlet是看不见tomcat加载的log4j类的,反之亦然。

在深入理解Java虚拟机,p278,提到:

tomcat为了支持权限目录结构,对目录中的类库进行加载和隔离,tomcat自定义了多个类加载器。

也说明了这点。

四、尝试绕过双亲委派
深入理解Java虚拟机,p231,写到:

双亲委派模型在jdk1.2引入,但它不是一个强制性的约束模型,而是Java设计者推荐给开发者的一种类加载方式。

换句话说,也就是可以不用这个模型的,自己实现类加载就可以,毕竟类加载器的原始作用就是:“通过类的全限定名得到类的二进制码流”

来看看双亲委派模型是如何实现的:

/**
     * Loads the class with the specified <a href="#name">binary name</a>.  The
     * default implementation of this method searches for classes in the
     * following order:
     *
     * <ol>
     *
     *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
     *   has already been loaded.  </p></li>
     *
     *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
     *   on the parent class loader.  If the parent is <tt>null</tt> the class
     *   loader built-in to the virtual machine is used, instead.  </p></li>
     *
     *   <li><p> Invoke the {@link #findClass(String)} method to find the
     *   class.  </p></li>
     *
     * </ol>
     *
     * <p> If the class was found using the above steps, and the
     * <tt>resolve</tt> flag is true, this method will then invoke the {@link
     * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
     *
     * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
     * #findClass(String)}, rather than this method.  </p>
     *
     * <p> Unless overridden, this method synchronizes on the result of
     * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
     * during the entire class loading process.
     *
     * @param  name
     *         The <a href="#name">binary name</a> of the class
     *
     * @param  resolve
     *         If <tt>true</tt> then resolve the class
     *
     * @return  The resulting <tt>Class</tt> object
     *
     * @throws  ClassNotFoundException
     *          If the class could not be found
     */
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // 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;
        }
    }

逻辑很清晰,优先给自己的parent加载器加载,特别注意,这里的父加载器,不是类的继承,因为三个classloader都是内存对象,所以他们只是逻辑上的父子关系。

(1)bootstrap classloader是native实现的

(2)extclassloader 和 APPclassloader 都是 URLclassloader 的子类对象

其实我想做的事情很简单,就是尝试自己写一个完全不依靠双亲委派的classloader,但是因为编码量比较大,所以我只尝试绕过APPclassloader,让自己的加载器继承(再次说明是逻辑上继承)于extclassloader

上面的代码已经显示,如果parent加载器没办法加载,就找子classloader的findclass方法,但是我想破坏这个模型,就必须重写classloader的loadclass方法



上代码:

package classloader;

import java.io.*;

/**
* Created by hupo.wh on 2016/7/18.
*/
public class OverwriteClassLoader extends ClassLoader {

    private String rootDir = "d:\\";

    public Class<?> loadClass(String name)
            throws ClassNotFoundException {

        synchronized (getClassLoadingLock(name)) {

            // First, check if the class has already been loaded
            Class<?> c = findClass(name);

            return c;
        }
    }

    private byte[] getClassData(String className) {
        //String path = classNameToPath(className);
        String path = "D:\\xiaohua\\WhTest\\target\\classes\\helloworld\\HelloWorld.class";
        try {
            InputStream ins = new FileInputStream(path);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead = 0;
            while ((bytesNumRead = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {

        byte[] classData = getClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        }
        else {
            System.out.println("name == "+name);
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private String classNameToPath(String className) {
        return rootDir + File.separatorChar
                + className.replace('.', File.separatorChar) + ".class";
    }

    public OverwriteClassLoader(ClassLoader classLoader) {

        super(classLoader);
    }

    protected final Class<?> whdefineClass(String name, byte[] b, int off, int len)
            throws ClassFormatError
    {
        return defineClass("helloworld.HelloWorld", b, off, len, null);
    }

}


/////Main.java
class Main{

    public static void main(String[] args) throws ClassNotFoundException {

        ClassLoader extcl =new Object(){}.getClass().getEnclosingClass().getClassLoader();
        while(extcl.getParent()!=null){

            extcl=extcl.getParent();
        }
        System.out.println("extcl == "+extcl);
        System.out.println("overwriteclassloader == "+OverwriteClassLoader.class.getClassLoader());

        OverwriteClassLoader cl = new OverwriteClassLoader(extcl);
        Class<?> clazz = cl.loadClass("helloworld.HelloWorld");

        System.out.println(clazz.getClassLoader());
    }
}

然而我的程序止步于一个地方:





详细跟断点进去,发现这个地方本来我要加载,helloworld.HelloWorld类,但是加载java.lang.Object类的时候,一个native方法又调回了我的classloader,进入第三部分的第1小部分。

SecurityException: Prohibited package name: java.lang



这又说明了一个问题,classloader去load这个类的父类,也是找我这个类,但是我这个类的loadclass没有双亲委派,同时安全检查又是用的classloader这个类内置的,所以通不过。
后来发现这段代码实际上是有问题的,因为我把InputStream写死了,下面代码才是正确的

其实这个时候,我自己加载的类已经绕过双亲委派了,因为自己这个类是没有去查父亲的,于是有了下面这个更极端的测试~~

package classloader;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
* Created by hupo.wh on 2016/7/20.
*/
public class ClassLoaderTest {

    public static void main(String[] args) throws Exception {

        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) {

                try {

                    InputStream is = null;
                    if(name == "helloworld.HelloWorld") {
                        is = new FileInputStream("D:\\xiaohua\\WhTest\\target\\classes\\helloworld\\HelloWorld.class");
                    }
                    else {

                        is = new FileInputStream("D:\\lang\\Object.class");
                        //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();
                }
                return null;
            }
        };


        Class<?> clazz = myLoader.loadClass("helloworld.HelloWorld");
        System.out.println(clazz.getClassLoader());
    }
}

我将rt.jar中的java.lang解压在d盘了,当然还是会报那个错误。。。



于是这样也会报错:

package java.lang;

/**
* Created by hupo.wh on 2016/7/20.
*/
public class sayHello {

    public static void main(String[] args) {
        System.out.println("hello");
    }
}


当然也是有方法的,就是完全自己实现classloader这个类,不继承于任何东西,这样的话,jvm也拿你没办法了。

附上深入理解Java虚拟机正确运行源码(p228)

package classloader;

import java.io.FileInputStream;
import java.io.InputStream;

/**
* Created by hupo.wh on 2016/7/20.
*/
public class ClassLoaderTest2 {

    public static void main(String[] args) throws Exception {

        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) {

                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 (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
        };


        Class<?> clazz = myLoader.loadClass("helloworld.HelloWorld");
        System.out.println(clazz.getClassLoader());

        Class<?> clazz1 = myLoader.loadClass("org.omg.CORBA.Any");
        System.out.println(clazz1.getClassLoader());
    }
}
参考资料
[1] 深入理解Java虚拟机
————————————————
版权声明:本文为CSDN博主「This is bill」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Scythe666/article/details/51956047
分享到:
评论

相关推荐

    Java类加载流程(双亲委派)流程图.zip

    首先,我们要明白什么是双亲委派模型。在Java中,每个类都由一个特定的类加载器来负责加载。当一个类加载器收到加载类的请求时,它不会立即加载,而是把这个任务委托给它的父类加载器去完成,直到到达顶层的启动类...

    Java中ClassLoader类加载学习总结

    为什么要破坏双亲委派模型呢?其实在某些情况下,我们可能需要破坏双亲委派模型,以满足特殊的业务需求。但是,这种做法需要非常小心,因为破坏双亲委派模型可能会导致类加载过程中的问题。 知识点五:ClassLoader...

    测试普通Java程序ClassLoader等级关系的Demo程序

    Java的类加载机制遵循“双亲委派模型”,这意味着当一个类加载器需要加载类时,它首先会委托其父类加载器尝试加载,只有当父类加载器无法加载时,才会自己尝试加载。 在Java的ClassLoader层次结构中,主要有以下几...

    面试题专场之源码和JVM篇.pdf

    接着,文档询问为什么要读源码以及读源码的收获。理想地,应聘者应该能够表达出阅读源码不仅仅是为了应付面试,而是为了提高编码能力、理解面向对象思想、设计模式以及解决实际开发中遇到的问题。同时,阅读源码也是...

    java陷阱常见面试题

    5. 类加载器:不恰当的类加载可能导致类冲突,理解双亲委派模型有助于避免这类问题。 二、Java客户端陷阱 1. GUI设计:Swing和JavaFX等库用于构建用户界面,但需要注意线程安全,避免在事件处理线程中执行耗时操作...

    Java ClassLoader定制实例

    在Java中,类的加载通常遵循“双亲委派模型”。这个模型的工作原理是:当一个ClassLoader接收到类加载请求时,它首先不会自己去加载,而是委托给父ClassLoader去尝试加载,只有当父ClassLoader无法加载时,子...

    The Java® Virtual Machine Specification Java SE 9 Edition.rar

    双亲委派模型是其典型特征,确保类的唯一性。 4. **字节码执行**:JVM通过解释器将字节码转换为机器码执行,Java 6之后引入了即时编译(JIT),将热点代码编译为本地机器代码,提升性能。 5. **垃圾收集**:JVM...

    java类的加载过程以及类加载器的分析

    类加载的双亲委派模型是指类加载器之间的委派关系,即每个类加载器都有一个父加载器,Bootstrap Loader是所有类加载器的父加载器。当我们想要加载一个类时,JVM会首先检查该类是否已经被加载,如果没有加载,则会...

    classloader-playground, 一个简单的java依赖隔离容器类.zip

    Java的类加载机制遵循双亲委派模型,即一个类首先会尝试由其父类加载器加载,只有当父类加载器无法完成加载时,才会由当前类加载器进行加载。这种机制保证了系统类(如java.lang.String)的一致性和安全性。 ...

    Java虚拟机

    同时,了解双亲委派模型也有助于实现自定义的类加载器。 4. **增强编程技能** - **多线程机制**:JVM提供了丰富的并发控制机制,如线程同步、锁机制等。深入理解这些机制可以帮助开发者编写更健壮的多线程程序。 ...

    Advanced Design and Implementation of Virtual Machines

    书中深入讨论了这些步骤,包括双亲委派模型、类加载器自定义等高级话题。 4. **内存管理**:JVM的内存区域,如堆、栈、方法区、本地方法栈等,是性能优化的关键。书中详细阐述了垃圾收集算法(如分代收集、标记-...

    独孤九剑-------面试题

    独孤九剑-------面试题 以下是从给定的文件中生成的相关知识点: Spring * Spring 优点:开源、免费、轻量级、非入侵式、支持事务处理、支持对...* 什么双亲委派模型?: + 一个类加载机制 + 支持多种类加载策略

    java虚拟机笔记及代码.zip

    8. **类加载机制**:双亲委派模型是JVM加载类的默认机制,它保证了类加载的唯一性,避免了类的重复加载和冲突。 9. **反射**:Java反射API允许程序在运行时动态访问类的信息,创建和操作类的对象,增强了程序的灵活...

    定位web开发中的jar包冲突

    默认情况下,Java使用双亲委派模型(Parent Delegation Model)加载类,即子类加载器先尝试加载,如果失败则委托给父类加载器,直至根加载器。这种机制旨在维护类加载的一致性,但也可能成为冲突的来源。 在描述的...

    java 面试综合题

    - 泛型:理解泛型的边界,知道通配符的使用,以及为什么要有类型擦除。 - 集合操作:掌握遍历、添加、删除元素的方法,了解并发修改异常(ConcurrentModificationException)。 3. **多线程**: - 线程的创建:...

    java面试评价表.doc

    3. 为什么要实现序列化接口?实现序列化接口可以将对象转换为字节流,以便于网络传输或文件存储。 4. HashMap 内部的数据结构是什么?HashMap 使用哈希表来存储键值对,底层实现是数组+链表结构。 Java 高级 1. ...

    java面试题,180多页,绝对良心制作,欢迎点评,涵盖各种知识点,排版优美,阅读舒心

    【JVM】双亲委派模型中,从顶层到底层,都是哪些类加载器,分别加载哪些类? 55 【JVM】能不能自己写个类叫java.lang.System? 57 【JVM】类的加载过程 58 【JVM】类的初始化 58 类什么时候才被初始化: 58 类的初始...

    java面试题库(含答案)

    - 类加载机制:理解双亲委派模型,以及如何自定义类加载器。 6. **异常处理** - 异常分类:了解检查异常和运行时异常的区别。 - try-catch-finally:掌握异常处理的基本结构,理解finally块的执行时机。 - ...

    深入java虚拟机第二版

    8.1.2 类装载器与双亲委派模型 8.1.3 常量池解析 8.1.4 解析CONSTANT_Class_info入口 8.1.5 解析CONSTANT_Fieldref_info 入口 S.1.6 解析CONSTANT_Methodref_info 入口 8.1.7 解析CONSTANT_Interface- ...

Global site tag (gtag.js) - Google Analytics