前言:在上文反射面试题中,提到JDBC使用反射来加载驱动程序,就是加载时运行了statci代码块。本文来主要讨论一下与此相关的类加载器和双亲委派机制。
https://blog.csdn.net/codehole_/article/details/100892463
https://blog.csdn.net/qq_31865983/article/details/100173173
下面是loadClass方法(所有加载器都一样,)的源码
protected synchronized Class<?> loadClass(String name,boolean resolve)throws ClassNotFoundException{ // 首先找缓存是否有 class Class c=findLoadedClass(name); if(c==null){ //没有判断有没有父类 try{ if(parent!=null){ //有的话,用父类递归获取 class 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){ // 如果还是没有找到,调用 findClass(name)去找这个类 c=findClass(name);} } if(resolve){ resolveClass(c);} return c; }
很明显的步骤:1.现在内存里找,没有找到委派到父类去找2.没有父类去bootstrap里找3.父类都没有找到调用自己的findClass查找,如下图,上层的父类加载器无法委派到下层
还是从面试题入手:
1.有哪些类加载器?
BootstrapClassLoader 负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME/lib/rt.jar 文件中,我们常用内置库 java.xxx.* 都在里面,比如 java.util.*、java.io.*、java.nio.*、java.lang.* 等等。这个 ClassLoader 比较特殊,它是由 C 代码实现的,我们将它称之为「根加载器」。
ExtensionClassLoader 负责加载 JVM 扩展类,比如 swing 系列、内置的 js 引擎、xml 解析器 等等,这些库名通常以 javax 开头,它们的 jar 包位于 JAVA_HOME/lib/ext/*.jar 中,有很多 jar 包。
AppClassLoader 才是直接面向我们用户的加载器,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录。我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的
2.什么是双亲委派机制?
上面的源代码和图里里的逻辑判断流程就是双亲委派机制,子类加载器在加载一个类的时候,总是先递归调用父类那里加载,父类加载不到,再从本加载器加载。
3.双亲委派机制起到什么作用?
双亲委派机制是JVM的一种安全机制,一是避免了类的重复加载,二是避免了核心API被篡改。
4.如果父类加载器想调用子类加载器怎么办(破坏双亲委派机制)?
常见的例子JDBC:在Java应用中存在着很多服务提供者接口(Service Provider Interface,SPI),这些接口允许第三方为它们提供实现, 如常见的 SPI 有 JDBC、JNDI等,这些 SPI 的接口属于 Java 核心库,一般存在rt.jar包中,由Bootstrap类加载器加载, 而 SPI 的第三方实现代码则是作为Java应用所依赖的 jar 包被存放在classpath路径下, 由于SPI接口中的代码经常需要加载具体的第三方实现类并调用其相关方法,但SPI的核心接口类是由引导类加载器来加载的, 而Bootstrap类加载器无法直接加载SPI的实现类,同时由于双亲委派模式的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载器SPI的实现类。
通过线程的ContextClassLoader(ServiceLoader)上下文类加载器实现。
具体可以参考:https://www.jianshu.com/p/bd94ae0d916e和https://blog.csdn.net/weixin_37195606/article/details/82805300
5.类的初始化步骤
1.如果这个类还没有被加载和链接,那先进行加载和链接;
2.假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口);
3.假如类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。
4、描述一下 JVM 加载 class?
JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。
1.由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。
2.当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。
3.当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;如果类中存在初始化语句,就依次执行这些初始化语句。
4.类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。
5.从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。
以上两题可结合这篇文章看:https://blog.csdn.net/noaman_wgs/article/details/74489549
来源:https://www.cnblogs.com/p6545244/p/15962216.html
本站部分图文来源于网络,如有侵权请联系删除。