本文目录

  • 前言
  • 一、类加载器
    • 1.1 类加载机制的基本特征
    • 1.2 类加载的分类
    • 1.3 类加载器
      • A、启动类加载器(引导类加载器,Bootstrap ClassLoader)
      • B、扩展类加载器(Extension ClassLoader)
      • C、应用程序类加载器(系统类加载器,AppClassLoaer)
      • D、自定义类加载器
    • 1.4 类加载器的必要性
  • 二、双亲委派模型
    • 2.1 概述
    • 2.2 双亲委派模型的实现
    • 2.3 双亲委派模型优劣势
      • 2.3.1 优势
      • 2.3.2 劣势
    • 2.4 破坏双亲委派模型
      • 2.4.1 第一次破坏双亲委派模型
      • 2.4.2 第二次破坏双亲委派模型
      • 2.4.3 第三次破坏双亲委派模型
  • 三、自定义类加载器
    • 3.1 为啥要自定义类加载器?
    • 3.2 常见的场景
    • 3.3 实现方式
      • 3.3.1 实现方式
      • 3.3.2 两种实现方式的对比
      • 3.3.3 说明

前言

之前被问到双亲委派模型和如果没有此模型会导致什么问题,我去,一下子懵了,但下来细想,这不就是问的此模型的优势嘛,我竟然没有答出来,应该是我紧张了,自以为把此模型掌握的很扎实,实则不然,所以再来整理一下相关的知识点。

一、类加载器

说到双亲委派模型,不得不提一下类加载器,再说类加载器之前不得不说类加载的分类和类加载机制。

1.1 类加载机制的基本特征

类加载机制通常有三个基本特征:
A、双亲委派模型:首先说明并不是所有类加载都遵循这个模型。有的时候,启动类加载器所加载的类型,是可能要加载用户代码的,比如JDK内部的SPI机制,用户可以在标准API框架上提供自己的实现,JDK也需要提供些默认的参考实现。比如,java中JNDI,JDBC,文件系统等很多方面,都是利用的这种机制,这种情况就不会用双亲委派模型去加载,而是利用所谓的上下文加载器。
B、可见性:子类加载器可以访问父类加载器加载的类型,凡是反过来是不允许的。不然,因为缺少必要的隔离,我们就没有办法利用类加载器去实现容器的逻辑。
C、单一性:由于父加载器的类型对于子加载器是可见的,所以父加载器中加载过的类型,就不会在子加载器中重复加载。但是注意,类加载器“邻居”间,同一类型仍然可以被加载多次,因为互相并不可见。

1.2 类加载的分类

类的加载分为:显式加载 VS 隐式加载
class文件的显式加载与隐式加载的方式是指JVM加载class文件到内存的方式。
• 显式加载指的是在代码中通过调用ClassLoader加载class对象,如直接使用Class.forName("java.lang.String")this.getClass().getClassLoader().loadClass()加载class对象。
• 隐式加载是通过虚拟机自动加载到内存中,而不是直接在代码中调用ClassLoader的方法加载class对象,如在加载某个类的class文件时,该类的class文件中引用了另一个类的对象,此时额外引用的类将通过JVM自动加载到内存中。
在日常开发以上两种方式一般会混合使用。

代码示例一

package com.fanhf.javastudy.classloader;/**
* @author fanhf
* @Description 显式和隐式加载
* @date 2021-02-07 14:22
*/
public class UserTest{User user = new User();//隐式加载public static void main(String[] args){try{//显式加载方式1Class clazz = Class.forName("com.fanhf.javastudy.classloader.User");//显式加载方式2Class<?> aClass = ClassLoader.getSystemClassLoader().loadClass("com.fanhf.javastudy.classloader.User");}catch(ClassNotFoundException e){e.printStackTrace();}}
}
// User类
class User{static {System.out.println("我是User类的初始化");}
}

1.3 类加载器

JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。
从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。无论类加载器的类型如何划分,在程序中我们最常见的类加载器结构如下情况:

#mermaid-svg-FUB8qFrwydmVq8ci {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-FUB8qFrwydmVq8ci .error-icon{fill:#552222;}#mermaid-svg-FUB8qFrwydmVq8ci .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FUB8qFrwydmVq8ci .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-FUB8qFrwydmVq8ci .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FUB8qFrwydmVq8ci .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FUB8qFrwydmVq8ci .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FUB8qFrwydmVq8ci .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FUB8qFrwydmVq8ci .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FUB8qFrwydmVq8ci .marker.cross{stroke:#333333;}#mermaid-svg-FUB8qFrwydmVq8ci svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FUB8qFrwydmVq8ci g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-FUB8qFrwydmVq8ci g.classGroup text .title{font-weight:bolder;}#mermaid-svg-FUB8qFrwydmVq8ci .nodeLabel,#mermaid-svg-FUB8qFrwydmVq8ci .edgeLabel{color:#131300;}#mermaid-svg-FUB8qFrwydmVq8ci .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-FUB8qFrwydmVq8ci .label text{fill:#131300;}#mermaid-svg-FUB8qFrwydmVq8ci .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-FUB8qFrwydmVq8ci .classTitle{font-weight:bolder;}#mermaid-svg-FUB8qFrwydmVq8ci .node rect,#mermaid-svg-FUB8qFrwydmVq8ci .node circle,#mermaid-svg-FUB8qFrwydmVq8ci .node ellipse,#mermaid-svg-FUB8qFrwydmVq8ci .node polygon,#mermaid-svg-FUB8qFrwydmVq8ci .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FUB8qFrwydmVq8ci .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-FUB8qFrwydmVq8ci g.clickable{cursor:pointer;}#mermaid-svg-FUB8qFrwydmVq8ci g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-FUB8qFrwydmVq8ci g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-FUB8qFrwydmVq8ci .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-FUB8qFrwydmVq8ci .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-FUB8qFrwydmVq8ci .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-FUB8qFrwydmVq8ci .dashed-line{stroke-dasharray:3;}#mermaid-svg-FUB8qFrwydmVq8ci #compositionStart,#mermaid-svg-FUB8qFrwydmVq8ci .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-FUB8qFrwydmVq8ci #compositionEnd,#mermaid-svg-FUB8qFrwydmVq8ci .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-FUB8qFrwydmVq8ci #dependencyStart,#mermaid-svg-FUB8qFrwydmVq8ci .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-FUB8qFrwydmVq8ci #dependencyStart,#mermaid-svg-FUB8qFrwydmVq8ci .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-FUB8qFrwydmVq8ci #extensionStart,#mermaid-svg-FUB8qFrwydmVq8ci .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-FUB8qFrwydmVq8ci #extensionEnd,#mermaid-svg-FUB8qFrwydmVq8ci .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-FUB8qFrwydmVq8ci #aggregationStart,#mermaid-svg-FUB8qFrwydmVq8ci .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-FUB8qFrwydmVq8ci #aggregationEnd,#mermaid-svg-FUB8qFrwydmVq8ci .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-FUB8qFrwydmVq8ci .edgeTerminals{font-size:11px;}#mermaid-svg-FUB8qFrwydmVq8ci :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}

启动类加载器
BootStrap ClassLoader
扩展类加载器
Extension ClassLoader
应用程序类加载器
Application ClassLoader
用户自定义类加载器1
User ClassLoader
用户自定义类加载器2
User ClassLoader

除了顶层的启动类加载器外,其余的类加载器都应当有自己的“父类”加载器。
不同类加载器看似是继承(Inheritance)关系,实际上是包含关系。在下层加载器中,包含着上层加载器的引用

A、启动类加载器(引导类加载器,Bootstrap ClassLoader)

• 这个类加载使用C/C++语言实现的,嵌套在JVM内部
• 它用来加载java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容)。用于提供JVM自身需要的类。
• 并不继承自java.lang.ClassLoader,没有父加载器。
• 出于安全考虑,Bootstrap启动类加载器只加载包名为:java、javax、sun等开头的类
• 加载扩展类和应用程序类加载器,并指定为他们的父类加载器。

使用-XX:+TraceClassLoading参数得到。
在上面的代码示例一中,加入-XX:+TraceClassLoading的启动参数,可以看到

此处省略许多类…

此处省略许多类…

代码示例一中,加载了504个类,包括以下包的类
java.lang、java.util、java.io、java.nio、java.net、java.security、
sun.misc、sun.reflect、sun.nio、sun.usagetracker、sun.launcher

B、扩展类加载器(Extension ClassLoader)

• java语言编写,由sun.misc.Launcher$ExtClassLoader实现。
• 继承于ClassLoader类
• 父类加载器为启动类加载器

从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。

类加载器继承关系如下图:

ClassLoader的孩子们:

Launcher类中的实现

C、应用程序类加载器(系统类加载器,AppClassLoaer)

• java语言编写,由sun.misc.Launcher$AppClassLoader实现
• 继承于ClassLoader类
• 父类加载器为扩展类加载器
• 它负责加载环境变量classpath或系统属性 java.class.path指定路径下的类库
• 应用程序中的类加载器默认是系统类加载器
• 他是用户自定义类加载器的默认父加载器

通过ClassLoader的getSystemClassLoader() 方法可以获取到该类加载器

D、自定义类加载器

自定义类加载器可以实现应用隔离,比如Tomcat,Spring等中间件和组件框架都在内部实现了自定义的加载器。自定义类加载器通过需要继承自ClassLoader,此类中的loadClass方法中的逻辑就是双亲委派模型的实现,继承ClassLoader后,在jdk1.2之后,不建议去覆盖loadClass方法,而是在findClass方法中实现,findClass就是在loadClass中调用的,当loadClass方法中父类加载器加载失败,就会调用自己写的findClass方法来完成类的加载,这样就可以保证自定义的类加载器也符合双亲委派模型。

通过看源码得知,ClassLoader是一个抽象类,很多方法是空的没有实现,比如findClass()、findResource()等。而URLClassLoader这个实现为这些方法提供了具体的实现。并新增了URLClassPath类协助取得Class字节码流等功能。在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自己去编写findClass()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁。

1.4 类加载器的必要性

一般情况下,java开发人员并不需要在程序中显式的使用类加载器,但是了解类加载器的加载机制却很重要。从以下几点说明:
• 避免在开发中遇到java.lang.ClassNotFoundException异常或java.lang.NoClassDefFoundError异常时手足无措。只有了解类加载器的加载机制才能够在出现异常的时候快速的根据错误异常日志定位问题。
• 需要支持类的动态加载或需要对编译后的字节码文件进行加解密操作时,就需要和类加载器打交道了。
开发人员可以在程序中编写自定义类加载器来重新定义类的加载规则,以便实现一些自定义的处理逻辑。

赠送面试题:Class.forName()与ClassLoader.loadClass()

• Class.forName():是一个Class类中在静态方法,属于显式加载,最常用的是Class.forName(String className);
根据传入的类的全限定名返回一个Class对象。该方法在将Class文件加载到内存的同时,会执行类的初始化。如:Class.forName(“com.fanhf.javastudy.classloader.ClassLoaderTest”);

• ClassLoader.loadClass():这是一个实例方法,属于隐式加载,需要一个ClassLoader对象来调用此方法。该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化。该方法因为需要得到一个ClassLoader对象,所以可以根据需要指定使用哪个类加载器,如:ClassLoader cl=……;
cl.loadClass(“com.fanhf.javastudy.classloader.ClassLoaderTest”)

二、双亲委派模型

简单说完了类加载器和类加载机制,步入正题说一下双亲委派模型

2.1 概述

类加载器用来把类加载到java虚拟机中。从JDK1.2开始,类的加载过程采取双亲委派机制,这种机制能更好的保证java平台的安全。如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载器任务,就成功返回。只有父类加载器无法完成此加载任务时,才自己去加载。
如下图:

然而,java虚拟机规范中并没有明确要求类加载器的加载机制一定要使用双亲委派模型,只是建议使用这种方式。在tomcat中,缺省的类加载器接到一个类加载任务,会自行加载,加载失败才会委托给它的超类进行加载,这也是Servelet规范推荐的一种做法。

2.2 双亲委派模型的实现

双亲委派机制在java.lang.ClassLoader.loadClass(String,boolean)接口中体现。
重点分析一下loadClass方法

/*
*
* <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.
*
* 翻译为:如果使用上述步骤找到了该类,并且resolve标志为true,则此方法将在生成的Class上调用 resolveClass(Class)方法对象。
*/
protected  Class<?>loadClass(String name,boolean resolve) throws ClassNotFoundException //resolve为true,加载class的同时进行解析操作。
{synchronized(getClassLoadingLock(name)){ //同步操作,保证只能加载一次//First,check if the class has already been loaded//调用{@link findLoadedClass(String)},以检查是否已加载该类。//首先,在缓存中判断是否已经加载同名的类Class<?> c = findLoadedClass(name);if(c == null){long t0 = System.nanoTime();try{//获取当前类的父类加载器if(parent != null){//如果存在父类加载器,就调用父类的加载器进行类的加载c = parent.loadClass(name,false);}else{//parent是null,说明父类加载器是引导类加载器c = findBootstrapClassOrNull(name);}}catch(ClassNotFoundException e){//ClassNotFoundException thrown if class not found//from the non-null parent classloader}if(c == null){ //当前类的加载器的父类加载器未加载此类 or 当前类的加载器未加载此类//If still not found,then invoke findClassinorder//to find the class.long t1 = System.nanoTime();c = findClass(name);//this is the defining classloader;record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1-t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if(resolve){ //是否进行解析操作resolveClass(c);}return c;}
}

实现逻辑:
1)先在当前类加载器缓存中查找有无目标类,如果不为空,则调用parent.loadClass(name,false)接口进行加载。
2)判断当前加载器的父类加载器是否为空,如果不为空,则调用parent.loadClass(name,false)接口进行加载。
3)反之,如果当前加载器的父类加载器为空,则调用findBootstrapClassOrNull(name)接口,让引导类加载器进行加载。
4)如果通过以上3条路径都没有加载成功,则调用findClass(name)进行加载。该接口最终调用java.lang.ClassLoader 接口的defineClass系列的native接口加载目标java类。

举个

java类加载和双亲委派模型浅说相关推荐

  1. 由源码深入Java类加载器(双亲委派模型)

    JVM类加载器 JVM主要有以下几种类加载器: 引导类加载器 主要加载JVM运行核心类库,位于JRE的lib目录下,如rt.jar中的类. 扩展类加载器 主要加载JVM中扩展类,位于JRE的ext目录 ...

  2. 面向对象回顾(静态变量、类加载机制/双亲委派模型、Object类的方法、类和对象区别)

    1. 静态变量存在什么位置? 方法区 2. 类加载机制,双亲委派模型,好处是什么? 某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务, ...

  3. Java类加载机制双亲委派机制

    关键知识点提炼: 类的唯一性:类的实例= 类加载器 ➕全限定类名 (扩展pandora容器隔离原理-类加载器隔离) 类加载过程:家(加)宴(验)准备了西(析)式菜. 加载-验证-准备-解析-初始化 双 ...

  4. java 打破双亲委派,为什么说java spi破坏双亲委派模型?

    虽然有SPI破坏双亲委派模型的说法,但我不太认同.简单说下. 双亲委派模型(再次吐槽下这个翻译),是一种加载类的约定.这个约定的一个用处是保证安全.比如说你写Java用了String类,你怎么保证你用 ...

  5. java类加载-ClassLoader双亲委派机制

    "类加载体系"及ClassLoader双亲委派机制.java程序中的 .java文件编译完会生成 .class文件,而 .class文件就是通过被称为类加载器的ClassLoade ...

  6. java 委派关系_一文读懂java类加载之双亲委派机制

    一个编译后的class文件,想要在JVM中运行,就需要先加载到JVM中.java中将类的加载工具抽象为类加载器,而通过加载工具加载类文件的具体方式被称为双亲委派机制. 知识点 类加载器:通过一个类全限 ...

  7. java委派_一文读懂java类加载之双亲委派机制

    作者:程序猿微录 出自:TinyRecord 一个编译后的class文件,想要在JVM中运行,就需要先加载到JVM中.java中将类的加载工具抽象为类加载器,而通过加载工具加载类文件的具体方式被称为双 ...

  8. 看完吊打面试官!java类加载机制双亲委派

    前言 周末花了2天时间学习了额RabbitMQ,总结了最核心的知识点,带大家快速掌握RabbitMQ,整理不易希望帮忙点赞,转发,分享下,谢谢 阿里的人才画像 其实最近两年自己一直在做面试官,也面试过 ...

  9. JVM—类加载器和双亲委派模型

    关注微信公众号:CodingTechWork,一起工作学习总结. 文章目录 引言 类加载器 类与类加载器关系 类加载器分类 启动类加载器 扩展类加载器 应用程序类加载器 双亲委派模型 介绍 工作流程 ...

最新文章

  1. C# 判断两张图片是否一致,极快速
  2. 理解 Kotlin 中的属性(property)
  3. EOS (3)系统特点
  4. 【小白学习keras教程】九、keras 使用GPU和Callbacks模型保存
  5. Go聊天室的思路:一个拨号 一个监听
  6. MySQL 建表字段长度的限制问题
  7. python定义区间[-5、5_有一个数 x 在区间 [-5,0] 内 , 写出其条件表达式 。 (5.0分)_学小易找答案...
  8. [react] react中的setState是同步还是异步的呢?为什么state并不一定会同步更新?
  9. 408业务课·计算机网络——【考研随笔】之一
  10. SqlHelper编写
  11. windows模拟微信小程序_Windows 版微信新版本内测!小程序可以直接添加到电脑桌面了...
  12. 树莓派竟出微控制器了!Raspberry Pi Pico 只需 4 美元!
  13. linux重启mysql一直_linux正确重启MySQL的方法
  14. java.lang.ClassCastException: $Proxy8 cannot be cast to org.hihernate.lob.SerialzableClob
  15. 词法分析器的java代码_利用Java实现简单的词法分析器实例代码
  16. 自定义数据集算子数据结构
  17. 证监会计算机类笔试上岸经验,公务员考试笔试166分上岸经验(全干货)
  18. 前端 Leader 是如何带领团队和建设团队文化的
  19. 《PPT思维》第一课:PPT基础思维
  20. java 日期处理_java日期处理总结

热门文章

  1. 【交通标志识别】基于模板匹配算法实现限速交通标志识别附matlab代码
  2. 趁着课余时间学点Python(十五)有趣的小模块
  3. excel公式应用大全
  4. 计算机英语人邮第三版教案,外研版高中英语选修6教案:Module 1 写作 -写一封电子邮件.doc...
  5. 23种设计模式-完结!
  6. mysql socket tcp udp_TCP/UDP/HTTP/SOCKET深入浅出
  7. # IEEE浮点表示
  8. TensorFlow与中文手写汉字识别
  9. [golang gin框架] 24.Gin 商城项目-redis讲解以及操作
  10. 用R语言实现神经网络预测股票实例