类加载器概述:

每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的”.class”文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载。
  那么 .class 文件什么时候会被类加载器加载到 JVM 中那?比如执行 new 操作的时候,我们使用 Class.forName(“ 包路径 + 类名 “)、Class.forName(“ 包路径 + 类名 “,ClassLoader)、ClassLoader.loadClass(“ 包路径 + 类名 “) 就触发了类加载器去类加载对应的路径去查找 *.class,并创建 Class 对象。另外需要注意的是除去 new 操作外,其他几种方式加载字节码到内存后只是生产一个 Class 对象,要产生具体的对象实例还需要使用 Class 对象 .newInstance() 函数来创建。

类的生命周期:

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。它们开始的顺序如下图所示:

加载:

通过类的全限定名来获取定义此类的二进制字节流

将此二进制字节流所代表的静态存储结构转化成方法区的运行时数据结构

在堆内存中生成代表此类的java.lang.Class对象,作为该类访问入口.
  

验证:

连接阶段第一步.验证的目的是确保Class文件的字节流中信息符合虚拟机的要求,不会危害虚拟机安全,使得虚拟机免受恶意代码的攻击.大致完成以下四个校验动作:

文件格式验证

源数据验证

字节码验证

符号引用验证

准备:连接阶段第二步,
正式为类变量分配内存并设置变量的初始值.(仅包含类变量,不包含实例变量).
解释如下:  
为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
默认初始值如下:

1.八种基本数据类型默认的初始值是0
2.引用类型默认的初始值是null
3.有static final修饰的会直接赋值,例如:static final int x=10;则默认就是10.
解析:连接阶段第三步,
虚拟机将常量池中的符号引用替换为直接引用,解析动作主要针对类或接口,字段,类方法,方法类型等等…
说白了就是jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址。
初始化:
类的初始化是类加载过程的最后一步,在该阶段,才真正意义上的开始执行类中定义的java程序代码.该阶段会执行类构造器.
类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。
使用:
使用该类所提供的功能.

卸载:
从内存中释放.
该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
加载该类的ClassLoader已经被回收
该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法
类加载器

java.lang.classLoader


 大部分java程序会使用以下3中系统提供的类加载器:

启动类加载器(Bootstrap ClassLoader):

这个类加载器负责将<JAVA_HOME>\lib目录下的类库加载到虚拟机内存中,用来加载java的核心库,此类加载器并不继承于java.lang.ClassLoader,不能被java程序直接调用,代码是使用C++编写的.是虚拟机自身的一部分.

扩展类加载器(Extendsion ClassLoader):
    这个类加载器负责加载<JAVA_HOME>\lib\ext目录下的类库,用来加载java的扩展库,开发者可以直接使用这个类加载器.

应用程序类加载器(Application ClassLoader):

这个类加载器负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的java类都是由这个类加载器加载,这个类加载器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器.一般情况下这就是系统默认的类加载器.

除此之外,我们还可以加入自己定义的类加载器,以满足特殊的需求,需要继承java.lang.ClassLoader类.
  
  **

类加载器的双亲委派模型:

**
  「双亲委托模式」指的就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,如果父类加载器可以完成类加载任务,就成功返回;**只有父类加载器无法完成此加载任务时,自己才去加载。
   双亲委托模型的工作过程:如果一个类加载器收到了一个类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委托给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成加载请求(它的搜索范围之中没有找到这个类)时,子加载器才会尝试着自己去加载。
  
**
  Class.forname()与ClassLoader.loadClass():
  Class.forname():是一个静态方法,最常用的是Class.forname(String className);根据传入的类的全限定名返回一个Class对象.该方法在将Class文件加载到内存的同时,会执行类的初始化.

如: Class.forName(“com.wang.HelloWorld”);

ClassLoader.loadClass():这是一个实例方法,需要一个ClassLoader对象来调用该方法,该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化.该方法因为需要得到一个ClassLoader对象,所以可以根据需要指定使用哪个类加载器.

如:ClassLoader cl=…;cl.loadClass(“com.wang.HelloWorld”);

Class.forname()与ClassLoader.loadClass():
  Class.forname():是一个静态方法,最常用的是Class.forname(String className);根据传入的类的全限定名返回一个Class对象.该方法在将Class文件加载到内存的同时,会执行类的初始化.

如: Class.forName(“com.wang.HelloWorld”);

ClassLoader.loadClass():这是一个实例方法,需要一个ClassLoader对象来调用该方法,该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化.该方法因为需要得到一个ClassLoader对象,所以可以根据需要指定使用哪个类加载器.

如:ClassLoader cl=…;cl.loadClass(“com.wang.HelloWorld”);

Class.forname()与ClassLoader.loadClass():
  Class.forname():是一个静态方法,最常用的是Class.forname(String className);根据传入的类的全限定名返回一个Class对象.该方法在将Class文件加载到内存的同时,会执行类的初始化.

如: Class.forName(“com.wang.HelloWorld”);

ClassLoader.loadClass():这是一个实例方法,需要一个ClassLoader对象来调用该方法,该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化.该方法因为需要得到一个ClassLoader对象,所以可以根据需要指定使用哪个类加载器.

如:ClassLoader cl=…;cl.loadClass(“com.wang.HelloWorld”);

(1)优点

避免类库重复加载
安全,将核心类库与用户类库隔离,用户不能通过加载器替换核心类库,如String类。
(2)弊端

委托永远是子加载器去请求父加载器,是单向的,即上层的类加载器无法访问下层的类加载器所加载的类:
举个例子,假设「BootStrap」中提供了一个接口,及一个创建其实例的工厂方法,但是该接口的实现类在「System」中,那么就会出现工厂方法无法创建在「System」加载的类的实例的问题。拥有这样问题的组件有很多,比如JDBC、Xml parser等。

3、如何解决弊端——使用「SPI」

「SPI」 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。 目前有不少框架用它来做服务的扩展发现, 简单来说,它就是一种动态替换发现的机制。
API和SPI的区别
API 直接被应用开发人员使用,SPI 被框架扩展人员使用
API Application Programming Interface

大多数情况下,都是实现方来制定接口并完成对接口的不同实现,调用方仅仅依赖却无权选择不同实现。

SPI Service Provider Interface

而如果是调用方来制定接口,实现方来针对接口来实现不同的实现。调用方来选择自己需要的实现方。

3、JDBC举例

下面以JDBC为例,介绍「SPI」机制。

在JDBC4.0之前,我们开发有连接数据库的时候,通常会用Class.forName(“com.mysql.jdbc.Driver”)这句先加载数据库相关的驱动,然后再进行获取连接等的操作。而JDBC4.0之后不需要用Class.forName(“com.mysql.jdbc.Driver”)来加载驱动,直接获取连接就可以了,现在这种方式就是使用了Java的「SPI」扩展机制来实现。
jdbc的获取连接的两种方式
添加链接描述

(1)接口定义

JDBC在java.sql.Driver只定义了接口。

(2)厂商实现

这里以MySQL为例,在mysql-connector-java-5.1.39.jar包里的META-INF/services目录下可以找到一个java.sql.Driver文件,文件内容是一个类名,这个名叫com.mysql.cj.jdbc.Driver的类就是MySQL针对JDBC中定义的接口的实现。


(3)如何使用

在我们的应用里面,我们就可以直接连接MySQL了。

Connection conn = DriverManager.getConnection(url,username,password);
显然语句并没有加载实现类,这里就涉及到使用「SPI」扩展机制来查找相关驱动了,接下来,我们结合源码探究一下这是如何实现的。

4、源码解析
关于驱动的查找其实都在DriverManager中,DriverManager位于java.sql包里,用来获取数据库连接,在DriverManager中有一个静态代码块如下:
static {
loadInitialDrivers();
println(“JDBC DriverManager initialized”);
}
loadInitialDrivers方法用于实例化驱动,由3部分构成:
(1)获取有关驱动的名称
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction() {
public String run() {
return System.getProperty(“jdbc.drivers”);
}
});
} catch (Exception ex) {
drivers = null;
}
drivers保存驱动的定义
(2)加载并实例化驱动

比较关键的地方是ServiceLoader.load
其中ServiceLoader.load(Driver.class)方法源码:
public static ServiceLoader load(Class service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}

   java的双亲委托类加载机制(ClassLoaderA -> System class loader -> Extension class loader -> Bootstrap class loader)可以保证核心类的正常安全加载。但是右边的 Bootstrap class loader 所加载的代码需要反过来去找委派链靠左边的 ClassLoader A 去加载东西的时候,就需要委派链左边的 ClassLoader 设置为线程的上下文加载器即可。

每一个线程都有自己的ContextClassLoader,默认以SystemClassLoader为ContextClassLoader。通过Thread.currentThread().getContextClassLoader(),可以把一个ClassLoader置于一个线程的实例之中,使该ClassLoader成为一个相对共享的实例,这样即使是启动类加载器中的代码也可以通过这种方式访问应用类加载器中的类了。
参考链接:
https://www.cnblogs.com/hiyujie/p/wo-xueJava1ClassLoader-yu-shuang-qin-wei-tuo-mo-sh.html

java类加载器以及spi相关推荐

  1. 深入探讨 Java 类加载器

    深入探讨 Java 类加载器 类加载器(class loader)是 Java™中的一个很重要的概念.类加载器负责加载 Java 类的字节代码到 Java 虚拟机中.本文首先详细介绍了 Java 类加 ...

  2. Java类加载器( 死磕9)

    [正文]Java类加载器(  CLassLoader ) 死磕9:  上下文加载器原理和案例 本小节目录 9.1. 父加载器不能访问子加载器的类 9.2. 一个宠物工厂接口 9.3. 一个宠物工厂管理 ...

  3. 视频学习笔记------系统学习让你轻松定义 Java 类加载器

    视频地址:https://www.bilibili.com/video/BV1vJ41177cw 简介 类加载就是将磁盘上的class文件加载到内存中.本课程详细分析了从编写一个类到编译.加载的整个过 ...

  4. 80070583类不存在_结合JVM源码谈Java类加载器

    一.前言 之前文章 加多:ClassLoader解惑​zhuanlan.zhihu.com 从Java层面讲解了Java类加载器的原理,这里我们结合JVM源码在稍微深入讲解下. 二.Java类加载器的 ...

  5. java类加载器_类加载器

    回顾一下类加载过程 类加载过程:加载->连接->初始化.连接过程又可分为三步:验证->准备->解析. 一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的 ...

  6. java 类加载器 解密_JAVA类加载器总结整理

    一.What(是什么?) 1.概念 Java类加载器是Java运行时环境的一部分,负责动态加载Java类到JVM的内存空间中.每个Java类必须由某个类加载器装入到内存中.每一个类加载器都有一个父类加 ...

  7. Java类加载器总结

    转载自  Java类加载器总结 1.类的加载过程   JVM将类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initialize)链接又分为三个步骤,如下图所示: 1) 装载:查 ...

  8. java 调用scala 类_如何使用java类加载器调用带参数的scala函数?

    我正在寻找一些将scala jar加载到java类加载器的指导. 当我使用java jar文件时,下面的函数对我有效. 其中,arr是一个java.net.URL数组,用于我需要加载到类加载器中的所有 ...

  9. java 类加载器-基础

    java 类加载器-基础 类加载机制 类加载器的双亲委托机制 自定义类加载路径 自定义类加载器 类加载机制 类加载器ClassLoader. – 负责查找,加载,校验字节码的应用程序. – java. ...

最新文章

  1. php编译7教程,PHP7 快速编译安装
  2. Android适配全面总结(二)
  3. python自动化测试看什么书-《自动化平台测试开发-Python测试开发实战》新书出版了...
  4. C# Settings使用小结
  5. muduo之EventLoop
  6. C/C++之预处理命令
  7. 【Linux】一步一步学Linux——traceroute命令(167)
  8. sqlserver2008 创建支持文件流的数据库
  9. mysql的启动 两种方式
  10. html 360 浏览器图片自适应,360浏览器看图模式 一键保存高清套图
  11. vm虚拟机怎么连接wifi_win7下安装的vmware虚拟机怎么接入无线局域网实现网络互联互通-网络教程与技术 -亦是美网络...
  12. Service Mesh中的通用数据平面API设计
  13. 手游客户端开发招聘要求
  14. Verilog实现快递柜
  15. win10 qq远程不上服务器未响应,win10系统QQ远程协助连不上的解决方法
  16. 免费领,自动化控制编程入门到开挂学习路径(附教程和软件工具)
  17. Docker搭建+项目实训(多次的作业的综合)
  18. 一个计算机爱好者眼里的隐私
  19. Digilent Analog Discovery示波器自定义编程方法指南
  20. PHP三位创始人传奇的诞生简介

热门文章

  1. Design System Application - Chapter 3 字号 Font Size
  2. Sublime Text 编译运行Java
  3. 根据进程关键词linux批量关闭进程
  4. 08-Django-基础篇-admin管理后台
  5. 手机静音html还有声音,为什么手机静音了还有声音
  6. html插图动物主题代码,分享9款用HTML5/CSS3制作的动物、人物动画
  7. 【EdgeX(11)】 :通过研究openvino项目发现一个好东西,CVAT项目数据标注工具,可以使用docker-compose进行本地部署,本地局域网中使用,也非常安全
  8. 解决Axios跨域问题(Axios跨域问题解决方案)
  9. 检测PPG信号的峰值
  10. CSS-外部引入方法