上一篇:类加载器-分类

一、概述

除了根类加载器之外,其他的类加载器都需要有自己的父加载器。从JDK1.2开始,类的加载过程采用双亲委派机制,这种机制能够很好的保护java程序的安全。除了虚拟机自带的根类加载器之外,其余的类加载器都有唯一的父加载器。比如,如果需要classLoader加载一个类时,该classLoader先委托自己的父加载器先去加载这个类,若父加载器能够加载,则由父加载器加载,否则才有classLoader自己加载这个类。即每个类加载器都很懒,加载类时都先让父加载器去尝试加载,一直到根类加载器,加载不到时自己才去加载。真正加载类的加载器我们叫做启动类加载器。注意,双亲委派机制的父子关系并非面向对象程序设计中的继承关系,而是通过使用组合模式来复用父加载器代码。

这种机制如下图所示:

public class ClassLoaderDemo1 {public static void main(String[] args) throws Exception{//演示类加载器的父子关系ClassLoader loader = ClassLoaderDemo1.class.getClassLoader();while(loader!=null){System.out.println(loader);loader = loader.getParent();}}
}
运行结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b6d3586

使用双亲委派机制的好处:

1、可以避免类的重复加载,当父类加载器已经加载了该类时,就没有必要子ClassLoader再加载一次。
2、考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Object的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Object,而直接返回已加载过的Object.class,这样便可以防止核心API库被随意篡改。

//定义一个类,注意包名
package java.lang;public class MyObject {}
//加载该类
public static void main(String[] args) {Class clazz = MyObject.class;System.out.println(clazz.getClassLoader());
}
//输出结果
Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang

因为java.lang包属于核心包,只能由根类加载器进行加载,而根据类加载的双亲委派机制,根类加载器是加载不到这个MyObject类的(自定义的),所以只能由AppClassLoader进行加载,而这又不是允许的,所以会报出“Prohibited package name: java.lang”(禁止的包名)错误。

二、ClassLoader

所有的类加载器(除了根类加载器)都必须继承java.lang.ClassLoader。它是一个抽象类,主要的方法如下:

1、loadClass

在ClassLoader的源码中,有一个方法loadClass(String name,boolean resolve),这里就是双亲委托模式的代码实现。从源码中我们可以观察到它的执行顺序。需要注意的是,只有父类加载器加载不到类时,会调用findClass方法进行类的查找,所以,在定义自己的类加载器时,不要覆盖掉该方法,而应该覆盖掉findClass方法。

//ClassLoader类的loadClass源码
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> 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 statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}

API介绍:

2、findClass

在自定义类加载器时,一般我们需要覆盖这个方法,且ClassLoader中给出了一个默认的错误实现。

protected Class<?> findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name);
}

3、defineClass

该方法的签名如下。用来将byte字节解析成虚拟机能够识别的Class对象。defineClass()方法通常与findClass()方法一起使用。在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法获取要加载类的字节码,然后调用defineClass()方法生成Class对象。

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

4、resolveClass

连接指定的类,类加载器可以使用此方法来连接类。

三、URLClassLoader

在java.net包中,JDK提供了一个更加易用的类加载器URLClassLoader,它扩展了ClassLoader,能够从本地或者网络上指定的位置加载类。我们可以使用该类作为自定义的类加载器使用。

构造方法:

  1. public URLClassLoader(URL[] urls):指定要加载的类所在的URL地址,父类加载器默认为系统类加载器。
  2. public URLClassLoader(URL[] urls, ClassLoader parent):指定要加载的类所在的URL地址,并指定父类加载器。

案例1:加载磁盘上的类

public static void main(String[] args) throws Exception{File file = new File("d:/");URI uri = file.toURI();URL url = uri.toURL();URLClassLoader classLoader = new URLClassLoader(new URL[]{url});System.out.println(classLoader.getParent());Class aClass = classLoader.loadClass("com.itheima.Demo");Object obj = aClass.newInstance();}

案例2:加载网络上的类

public static void main(String[] args) throws Exception{URL url = new URL("http://localhost:8080/examples/");URLClassLoader classLoader = new URLClassLoader(new URL[]{url});System.out.println(classLoader.getParent());Class aClass = classLoader.loadClass("com.itheima.Demo");aClass.newInstance();
}

四、自定义类加载器

我们如果需要自定义类加载器,只需要继承ClassLoader类,并覆盖掉findClass方法即可。

1、自定义文件类加载器

package com.itheima.base.classloader;import sun.applet.Main;import java.io.*;public class MyFileClassLoader extends ClassLoader {private String directory;//被加载的类所在的目录/*** 指定要加载的类所在的文件目录* @param directory*/public MyFileClassLoader(String directory,ClassLoader parent){super(parent);this.directory = directory;}protected Class<?> findClass(String name) throws ClassNotFoundException {try {//把类名转换为目录String file = directory+File.separator+name.replace(".", File.separator)+".class";//构建输入流InputStream in = new FileInputStream(file);//存放读取到的字节数据ByteArrayOutputStream baos = new ByteArrayOutputStream();byte buf[] = new byte[1024];int len = -1;while((len=in.read(buf))!=-1){baos.write(buf,0,len);}byte data[] = baos.toByteArray();in.close();baos.close();return defineClass(name,data,0,data.length);} catch (IOException e) {throw new RuntimeException(e);}}public static void main(String[] args) throws Exception {MyFileClassLoader myFileClassLoader = new MyFileClassLoader("d:/");Class clazz = myFileClassLoader.loadClass("com.itheima.Demo");clazz.newInstance();}
}

2、自定义网络类加载器

package com.itheima.base.classloader;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;public class MyURLClassLoader extends ClassLoader {private String url;public MyURLClassLoader(String url) {this.url = url;}protected Class<?> findClass(String name) throws ClassNotFoundException {try {String path = url+ "/"+name.replace(".","/")+".class";URL url = new URL(path);InputStream inputStream = url.openStream();ByteArrayOutputStream baos = new ByteArrayOutputStream();int len = -1;byte buf[] = new byte[1024];while((len=inputStream.read(buf))!=-1){baos.write(buf,0,len);}byte[] data = baos.toByteArray();inputStream.close();baos.close();return defineClass(name,data,0,data.length);} catch (Exception e) {e.printStackTrace();}return null;}public static void main(String[] args) throws Exception{MyURLClassLoader classLoader = new MyURLClassLoader("http://localhost:8080/examples");Class clazz = classLoader.loadClass("com.itheima.Demo");clazz.newInstance();}
}

3、热部署类加载器

当我们调用loadClass方法加载类时,会采用双亲委派模式,即如果类已经被加载,就从缓存中获取,不会重新加载。如果同一个class被同一个类加载器多次加载,则会报错。因此,我们要实现热部署让同一个class文件被不同的类加载器重复加载即可。但是不能调用loadClass方法,而应该调用findClass方法,避开双亲委托模式,从而实现同一个类被多次加载,实现热部署。

MyFileClassLoader myFileClassLoader1 = new MyFileClassLoader("d:/",null);
MyFileClassLoader myFileClassLoader2 = new MyFileClassLoader("d:/",myFileClassLoader1);
Class clazz1 = myFileClassLoader1.loadClass("com.itheima.Demo");
Class clazz2 = myFileClassLoader2.loadClass("com.itheima.Demo");
System.out.println("class1:"+clazz1.hashCode());
System.out.println("class2:"+clazz2.hashCode());
结果:class1和class2的hashCode一致MyFileClassLoader myFileClassLoader1 = new MyFileClassLoader("d:/",null);
MyFileClassLoader myFileClassLoader2 = new MyFileClassLoader("d:/",myFileClassLoader1);
Class clazz3 = myFileClassLoader1.findClass("com.itheima.Demo");
Class clazz4 = myFileClassLoader2.findClass("com.itheima.Demo");
System.out.println("class3:"+clazz3.hashCode());
System.out.println("class4:"+clazz4.hashCode());
结果:class1和class2的hashCode不一致

五、类的显式与隐式加载

类的加载方式是指虚拟机将class文件加载到内存的方式。

  • 显式加载是指在java代码中通过调用ClassLoader加载class对象,比如Class.forName(String name);this.getClass().getClassLoader().loadClass()加载类。
  • 隐式加载指不需要在java代码中明确调用加载的代码,而是通过虚拟机自动加载到内存中。比如在加载某个class时,该class引用了另外一个类的对象,那么这个对象的字节码文件就会被虚拟机自动加载到内存中。

六、线程上下文类加载器

在Java中存在着很多的服务提供者接口SPI,全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,这些接口一般由第三方提供实现,常见的SPI有JDBC、JNDI等。这些SPI的接口(比如JDBC中的java.sql.Driver)属于核心类库,一般存在rt.jar包中,由根类加载器加载。而第三方实现的代码一般作为依赖jar包存放在classpath路径下,由于SPI接口中的代码需要加载具体的第三方实现类并调用其相关方法,SPI的接口类是由根类加载器加载的,Bootstrap类加载器无法直接加载位于classpath下的具体实现类。由于双亲委派模式的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载SPI的具体实现类。在这种情况下,java提供了线程上下文类加载器用于解决以上问题。

线程上下文类加载器可以通过java.lang.Thread的getContextClassLoader()来获取,或者通过setContextClassLoader(ClassLoader cl)来设置线程的上下文类加载器。如果没有手动设置上下文类加载器,线程将继承其父线程的上下文类加载器,初始线程的上下文类加载器是系统类加载器(AppClassLoader),在线程中运行的代码可以通过此类加载器来加载类或资源。

显然这种加载类的方式破坏了双亲委托模型,但它使得java类加载器变得更加灵活。


我们以JDBC中的类为例做一下说明。在JDBC中有一个类java.sql.DriverManager,它是rt.jar中的类,用来注册实现了java.sql.Driver接口的驱动类,而java.sql.Driver的实现类一般都是位于数据库的驱动jar包中的。

java.sql.DriverManager的部分源码截图:

java.util.ServiceLoader的部分源码截图:

下一篇:Java类加载器入门

类加载器-双亲委派机制相关推荐

  1. JVM-白话聊一聊JVM类加载和双亲委派机制源码解析

    文章目录 Java 执行代码的大致流程 类加载loadClass的步骤 类加载器和双亲委派机制 sun.misc.Launcher源码解析 Launcher实例化 Launcher 构造函数 双亲委派 ...

  2. 类加载的双亲委派机制

    类加载器的双亲委派机制 ​ 除了根类加载器之外,其他的类加载器都需要有自己的父加载器.从JDK1.2开始,类的加载过程采用双亲委派机制,这种机制能够很好的保护java程序的安全.除了虚拟机自带的根类加 ...

  3. 类加载器双亲委托机制详解

    理论了解: 关于类加载器的双亲委托机制基本上都听说过,面试时可能偶尔也会被问到,但是可能都是网上去找了一个理论性的答案临时了解了一下,并未对它到底是个什么样的机制有深入的了解,所以接下来准备深入了解它 ...

  4. 类加载器双亲委派模式

    双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只 ...

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

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

  6. JVM 学习四:类加载之双亲委派机制与沙箱安全机制

    1 双亲委派机制 Java 虚拟机对 Class 文件的加载采用的是按需加载的方式,也就是说:当需要使用该类时才会将它的 Class 文件加载到内存生成 Class 对象,而且加载某个类的 Class ...

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

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

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

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

  9. 关于类加载的双亲委派机制简单总结

    前言 我们知道,一个编译后的class文件,想要在JVM中运行,就需要先加载到JVM中.而做这个工作的老哥就是类加载器.而通过类加载器,加载类文件的具体方式被称为双亲委派机制. 什么是类加载器?有哪些 ...

最新文章

  1. mysql select from user_mysql查询[select * from user limit 0, 10;]
  2. mysql错误修改数据_使用正则表达式快速修改mysql中错误的varchar类型数据
  3. json中omitempty字段的使用
  4. Spring Bean 后置处理器PostProcessor
  5. linux卡在nfs挂载怎么办,51CTO博客-专业IT技术博客创作平台-技术成就梦想
  6. 手把手教你编写接口需求文档
  7. 容器编排技术 -- kubernetes设计理念
  8. 为什么Redux需要reducer成为“纯函数”
  9. python do while语句_python控制语句执行流程(while)
  10. Sklearn之Ensemble 估计器
  11. 使用servlet原生API作为参数
  12. HTTP劫持 方面了解和学习
  13. 微信文章互阅python脚本,一天可刷120分
  14. python读取svg转emf_玩玩矢量图标,SVG转换EMF
  15. mac/windows用Chrome浏览器截取长图
  16. 手机版wps怎么制作折线图_PPT制作必修课下载-PPT制作必修课app官方版v1.0手机版下载...
  17. 林下仿野生天麻的种植技术方法
  18. 学习ui设计软件有哪些
  19. 全球部分免费开放的电子图书馆
  20. springboot 2.x升级后出现Spring Security – There is no PasswordEncoder mapped for the id “null”的解决方案

热门文章

  1. 用JavaScript实现文件的上传与下载
  2. 【插件】浏览器广告拦截插件| 浏览器搜索广告横飞怎么办
  3. iOS 录音pcm获取当前音量
  4. i510500和i510400参数对比 区别大吗
  5. Linux解决FTP服务器不支持软连接目录
  6. html 串行通信接口,接口类型怎么区分串口和并口?
  7. Spring Boot项目学习15之我的主页和用户中心模块
  8. 海康威视摄像头rtsp视频流实现浏览器h5播放(无需浏览器安装插件)
  9. 【Linux学习】虚拟机VMware 安装ROS 一条龙教程+部分报错解决
  10. java中文件流close_Java FileInputStream close()方法与示例