`

双亲委派模型知识点

阅读更多
一。
JVM设计者把类加载阶段中的“通过'类全名'来获取定义此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。

1.类与类加载器

对于任何一个类,都需要由加载它的类加载器和这个类来确立其在JVM中的唯一性。也就是说,两个类来源于同一个Class文件,并且被同一个类加载器加载,这两个类才相等。

2.双亲委派模型

从虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),该类加载器使用C++语言实现,属于虚拟机自身的一部分。另外一种就是所有其它的类加载器,这些类加载器是由Java语言实现,独立于JVM外部,并且全部继承自抽象类java.lang.ClassLoader。



“双亲委派”机制只是Java推荐的机制,并不是强制的机制。

我们可以继承java.lang.ClassLoader类,实现自己的类加载器。如果想保持双亲委派模型,就应该重写findClass(name)方法;如果想破坏双亲委派模型,可以重写loadClass(name)方法。具体细节建议看下源码

————————————————
版权声明:本文为CSDN博主「大魔王King」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_39222112/article/details/81316511

二。破坏双亲委派模型

假如我们自己写了一个java.lang.String的类,我们是否可以替换调JDK本身的类?

答案是否定的。我们不能实现。为什么呢?我看很多网上解释是说双亲委托机制解决这个问题,其实不是非常的准确。因为双亲委托机制是可以打破的,你完全可以自己写一个classLoader来加载自己写的java.lang.String类,但是你会发现也不会加载成功,具体就是因为针对java.*开头的类,jvm的实现中已经保证了必须由bootstrp来加载。

不遵循“双亲委托机制”的场景

上面说了双亲委托机制主要是为了实现不同的ClassLoader之间加载的类的交互问题,被大家公用的类就交由父加载器去加载,但是Java中确实也存在父类加载器加载的类需要用到子加载器加载的类的情况。下面我们就来说说这种情况的发生。

Java中有一个SPI(Service Provider Interface)标准,使用了SPI的库,比如JDBC,JNDI等,我们都知道JDBC需要第三方提供的驱动才可以,而驱动的jar包是放在我们应 用程序本身的classpath的,而jdbc 本身的api是jdk提供的一部分,它已经被bootstrp加载了,那第三方厂商提供的实现类怎么加载呢?这里面JAVA引入了线程上下文类加载的概念,线程类加载器默认会从父线程继承,如果没有指定的话,默认就是系统类加载器(AppClassLoader),这样的话当加载第三方驱动的时候,就可 以通过线程的上下文类加载器来加载。
另外为了实现更灵活的类加载器OSGI以及一些Java appserver也打破了双亲委托机制。


JVM自带的ClassLoader类

JDK中提供了三个ClassLoader,根据层级从高到低为:

Bootstrap ClassLoader,主要加载JVM自身工作需要的类。
Extension ClassLoader,主要加载%JAVA_HOME%\lib\ext目录下的库类。
Application ClassLoader,主要加载Classpath指定的库类,一般情况下这是程序中的默认类加载器,也是ClassLoader.getSystemClassLoader() 的返回值。(这里的Classpath默认指的是环境变量中配置的Classpath,但是可以在执行Java命令的时候使用-cp 参数来修改当前程序使用的Classpath)



什么是双亲委托模型
JVM加载类的实现方式,我们称为 双亲委托模型:

如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委托给自己的父加载器,每一层的类加载器都是如此,因此所有的类加载请求最终都应该传送到顶层的Bootstrap ClassLoader中,只有当父加载器反馈自己无法完成加载请求时,子加载器才会尝试自己加载。

双亲委托模型加载类的过程
我们用一张简单的图片来描述 “使用自定义ClassLoader加载一个类的过程”:




自定义ClassLoader向自己的上层(Application ClassLoader)请求
Application ClassLoader继续向上层(Extension ClassLoader)请求
Extension ClassLoader继续向上层(Bootstrap ClassLoader)请求
Bootstrap ClassLoader是最上层类加载器,所以它尝试在自己的路径中查找要加载类,如果查找到了就加载类,否则向下层(Extension ClassLoader)反馈自己无法加载类。
Extension ClassLoader从自己的路径中寻找要加载类,找到则加载,找不到则继续向下返回。
Application ClassLoader从自己的路径中寻找要加载类,找到则加载,找不到则继续向下返回。
自定义ClassLoader从自己的路径中寻找要加载类,找到则加载。由于类加载请求是自定义ClassLoader发起的,所以当它自己也找不到要加载的类时会终止加载并抛出
ClassNotFoundException。
为什么要用双亲委托模型
双亲委托模型的重要用途是为了解决类载入过程中的安全性问题。

假设有一个开发者自己编写了一个名为java.lang.Object的类,想借此欺骗JVM。现在他要使用自定义ClassLoader来加载自己编写的java.lang.Object类。然而幸运的是,双亲委托模型不会让他成功。因为JVM会优先在Bootstrap ClassLoader的路径下找到java.lang.Object类,并载入它。

Java的类加载是否一定遵循双亲委托模型

这个答案是否定的。
双亲委托模型只是JDK提供的ClassLoader类的实现方式。
在实际开发中,我们可以通过自定义ClassLoader,并重写父类的loadClass方法,来打破这一机制。

---------------------
作者:markzy
来源:CSDN
原文:https://blog.csdn.net/markzy/article/details/53192993
版权声明:本文为博主原创文章,转载请附上博文链接!

总结:

1. $java_home/lib 目录下的java核心api

2. $java_home/lib/ext 目录下的java扩展jar包

3. java -classpath/-Djava.class.path所指的目录下的类与jar包

4. $CATALINA_HOME/common目录下按照文件夹的顺序从上往下依次加载

5. $CATALINA_HOME/server目录下按照文件夹的顺序从上往下依次加载

6. $CATALINA_BASE/shared目录下按照文件夹的顺序从上往下依次加载

7. 我们的项目路径/WEB-INF/classes下的class文件

8. 我们的项目路径/WEB-INF/lib下的jar文件

在同一个文件夹下,jar包是按顺序从上到下依次加载



附:
JDK默认ClassLoader

JDK 默认提供了如下几种ClassLoader

1.  Bootstrp loader
Bootstrp加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classes中的类。

1.  ExtClassLoader 
Bootstrp loader加载ExtClassLoader,并且将ExtClassLoader的父加载器设置为Bootstrploader.ExtClassLoader是用Java写的,具体来说就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中类库。

2.  AppClassLoader
Bootstrp loader加载完ExtClassLoader后,就会加载AppClassLoader,并且将AppClassLoader的父加载器指定为 ExtClassLoader。AppClassLoader也是用Java写成的,它的实现类是sun.misc.Launcher$AppClassLoader,另外我们知道ClassLoader中有个getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要负责加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器。

双亲委托模型

Java中ClassLoader的加载采用了双亲委托机制,采用双亲委托机制加载类的时候采用如下的几个步骤:

1.  当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。

每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。

2.  当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.

3.  当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。

说到这里大家可能会想,Java为什么要采用这样的委托机制?理解这个问题,我们引入另外一个关于Classloader的概念“命名空间”, 它是指要确定某一个类,需要类的全限定名以及加载此类的ClassLoader来共同确定。也就是说即使两个类的全限定名是相同的,但是因为不同的ClassLoader加载了此类,那么在JVM中它是不同的类。明白了命名空间以后,我们再来看看委托模型。采用了委托模型以后加大了不同的 ClassLoader的交互能力,比如上面说的,我们JDK本生提供的类库,比如hashmap,linkedlist等等,这些类由bootstrp 类加载器加载了以后,无论你程序中有多少个类加载器,那么这些类其实都是可以共享的,这样就避免了不同的类加载器加载了同样名字的不同类以后造成混乱。

如何自定义ClassLoader

Java除了上面所说的默认提供的classloader以外,它还容许应用程序可以自定义classloader,那么要想自定义classloader我们需要通过继承java.lang.ClassLoader来实现,接下来我们就来看看再自定义Classloader的时候,我们需要注意的几个重要的方法:

1.loadClass 方法

loadClass method declare

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

上面是loadClass方法的原型声明,上面所说的双亲委托机制的实现其实就实在此方法中实现的。下面我们就来看看此方法的代码来看看它到底如何实现双亲委托的。

loadClass method implement

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



return loadClass(name, false);

}

从上面可以看出loadClass方法调用了loadcClass(name,false)方法,那么接下来我们再来看看另外一个loadClass方法的实现。

Class loadClass(String name, boolean resolve)



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

throws ClassNotFoundException  

{  // First, check if the class has already been loaded  Class c = findLoadedClass(name);

//检查class是否已经被加载过了  if (c == null)

{    

try {     

if (parent != null) {        

c = parent.loadClass(name, false); //如果没有被加载,且指定了父类加载器,则委托父加载器加载。   

  } else {       

  c = findBootstrapClass0(name);//如果没有父类加载器,则委托bootstrap加载器加载      }

     } catch (ClassNotFoundException e) {        

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

// to find the class.        

c = findClass(name);//如果父类加载没有加载到,则通过自己的findClass来加载。      }

}

if (resolve)

{    

resolveClass(c);



return c;

}

上面的代码,我加了注释通过注释可以清晰看出loadClass的双亲委托机制是如何工作的。 这里我们需要注意一点就是public Class<?>loadClass(String name) throws ClassNotFoundException没有被标记为final,也就意味着我们是可以override这个方法的,也就是说双亲委托机制是可以打破的。另外上面注意到有个findClass方法,接下来我们就来说说这个方法到底是搞末子的。

2.findClass

我们查看java.lang.ClassLoader的源代码,我们发现findClass的实现如下:

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



throw new ClassNotFoundException(name);

}

我们可以看出此方法默认的实现是直接抛出异常,其实这个方法就是留给我们应用程序来override的。那么具体的实现就看你的实现逻辑了,你可以从磁盘读取,也可以从网络上获取class文件的字节流,获取class二进制了以后就可以交给defineClass来实现进一步的加载。defineClass我们再下面再来描述。ok,通过上面的分析,我们可以得出如下结论:

我们在写自己的ClassLoader的时候,如果想遵循双亲委托机制,则只需要overridefindClass.

3.defineClass

我们首先还是来看看defineClass的源码:

defineClass

protected final Class<?> defineClass(String name, byte[] b, int off, int len) 

throws ClassFormatError

{    

return defineClass(name, b, off, len, null);

}

从上面的代码我们看出此方法被定义为了final,这也就意味着此方法不能被Override,其实这也是jvm留给我们的唯一的入口,通过这个唯 一的入口,jvm保证了类文件必须符合Java虚拟机规范规定的类的定义。此方法最后会调用native的方法来实现真正的类的加载工作。

Ok,通过上面的描述,我们来思考下面一个问题:
假如我们自己写了一个java.lang.String的类,我们是否可以替换调JDK本身的类?

答案是否定的。我们不能实现。为什么呢?我看很多网上解释是说双亲委托机制解决这个问题,其实不是非常的准确。因为双亲委托机制是可以打破的,你完全可以自己写一个classLoader来加载自己写的java.lang.String类,但是你会发现也不会加载成功,具体就是因为针对java.*开头的类,jvm的实现中已经保证了必须由bootstrp来加载。

不遵循“双亲委托机制”的场景

上面说了双亲委托机制主要是为了实现不同的ClassLoader之间加载的类的交互问题,被大家公用的类就交由父加载器去加载,但是Java中确实也存在父类加载器加载的类需要用到子加载器加载的类的情况。下面我们就来说说这种情况的发生。

Java中有一个SPI(Service Provider Interface)标准,使用了SPI的库,比如JDBC,JNDI等,我们都知道JDBC需要第三方提供的驱动才可以,而驱动的jar包是放在我们应 用程序本身的classpath的,而jdbc 本身的api是jdk提供的一部分,它已经被bootstrp加载了,那第三方厂商提供的实现类怎么加载呢?这里面JAVA引入了线程上下文类加载的概念,线程类加载器默认会从父线程继承,如果没有指定的话,默认就是系统类加载器(AppClassLoader),这样的话当加载第三方驱动的时候,就可 以通过线程的上下文类加载器来加载。
另外为了实现更灵活的类加载器OSGI以及一些Java appserver也打破了双亲委托机制。
---------------------
作者:低调的洋仔
来源:CSDN
原文:https://blog.csdn.net/wangyang1354/article/details/49448007
版权声明:本文为博主原创文章,转载请附上博文链接!
分享到:
评论

相关推荐

    Java中ClassLoader类加载学习总结

    知识点一:双亲委派模型 双亲委派模型是一个基本的类加载模型,在需要加载一个类的时候,我们首先判断该类是否已被加载,如果没有就判断是否已被父加载器加载,如果还没有再调用自己的findClass方法尝试加载。这个...

    jvm基础学习,介绍各种jvm里面的知识点,和深入理解java虚拟机很像,易懂

    类加载过程中采用的双亲委派模型是一种层次结构的类加载器体系结构,它的工作方式如下: 1. 当一个类加载器收到加载类的请求时,它首先尝试让其父类加载器加载该类;如果父类加载器不存在或无法加载,则自行尝试...

    JAVA核心面试知识点整理.pdf

    双亲委派模型是类加载器的一个实现方式,它确保了Java平台的安全。 OSGI(Open Service Gateway Initiative)是一个动态模型系统,提供了服务的模块化编程,支持热插拔功能,使得Java的模块化编程成为可能。 以上...

    JAVA核心知识点整理.pdf

    类加载器负责加载类,它分为启动类加载器、扩展类加载器和应用程序类加载器,以及双亲委派模型保证了Java类的加载安全。OSGi(Open Service Gateway Initiative)是一种动态模型系统,支持模块化编程和热插拔功能。 ...

    JAVA核心知识点整理

    Java类加载器采用双亲委派模型,确保了Java核心库的类型安全。OSGi是一种基于Java的动态模块化系统,它允许模块化编程,支持热插拔功能,可以在运行时动态地加载、卸载和替换模块,而无需重启应用程序。 以上就是对...

    2020程序员必看这份JVM大厂高频面试题与知识点整合!.pdf,这是一份不错的文件

    8. 类加载器的双亲委派模型:类加载器的双亲委派模型是JVM的类加载机制的一部分。 9. JVM的线程机制:JVM的线程机制包括线程的创建、线程的执行、线程的同步等。 10. JVM的性能优化:JVM的性能优化包括JIT编译、GC...

    2021面试必备--JAVA核心知识点整理.pdf

    类加载器分为启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader),它们遵循双亲委派模型来加载类。OSGi(Open Service Gateway Initiative)是...

    java程序员需要掌握的知识点

    - **类加载机制**:了解类是如何被加载到JVM中的,包括双亲委派模型等概念。 - **性能调优**:学习如何使用工具监控JVM性能并进行调优,例如使用JVisualVM、JConsole等工具。 #### 3. 框架与技术栈 熟练掌握常见的...

    Java类加载机制.pdf

    类加载机制涉及类加载顺序、类加载器的体系结构、类加载过程以及双亲委派模型等核心概念。架构师或高级开发人员必须深刻理解这些知识点,以便在进行大型互联网平台架构设计和开发时做出正确的决策。 首先,类加载指...

    JVM与性能优化知识点整理.zip

    - **双亲委派模型**:类加载时,先由父加载器尝试加载,失败后才由子加载器加载,确保类的唯一性。 - **类加载器**:Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader和自定义类加载器。 5. **...

    Java基础知识点 - 内容比较全面

    类加载遵循双亲委派模型,确保类加载的唯一性。 6. **Java中的synchronized使用**:synchronized用于实现线程同步,可以修饰方法或代码块,确保同一时刻只有一个线程执行特定代码,防止数据不一致。 7. **Java中的...

    JAVA高级知识点整理.rar

    这包括垃圾收集机制(如分代收集、并行与并发收集)、内存模型(堆、栈、方法区、本地方法栈)以及类加载机制(双亲委派模型)。JVM调优涉及到堆内存大小设置、垃圾收集器选择、线程池配置等多个方面。 再者,Java ...

    JAVA核心知识点.pdf

    类加载器通过双亲委派模型实现,保证Java类的唯一性。 Java IO包主要负责执行字节流和字符流的输入/输出操作。而NIO则通过使用缓冲区、通道、选择器来提供非阻塞的I/O操作,适用于需要处理大量连接的应用程序。 类...

    Java的jvm相关知识点

    - 双亲委派模型:类加载时先询问父加载器,若父加载器无法加载则递归至顶层,确保类的唯一性,但也可能导致包名冲突和性能影响。 5. 类加载步骤: - 查找类:根据类全名定位到字节码文件。 - 读取类:读取字节码...

    java核心知识.pdf

    - 类加载器的结构和双亲委派模型 - OSGI(动态模型系统) 5. Java集合框架: - 集合框架的接口继承关系和具体实现类 - List接口及其实现类ArrayList、Vector、LinkedList - Set接口及其实现类HashSet、TreeSet...

Global site tag (gtag.js) - Google Analytics