博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【JVM】类加载器及双亲委派机制实例解析
阅读量:4843 次
发布时间:2019-06-11

本文共 6538 字,大约阅读时间需要 21 分钟。

一、类加载器及双亲委派机制介绍

在JVM中,一个类被加载到虚拟机这个过程包括有3个步骤,即加载、连接和初始化。而加载这个过程,就是由类加载器ClassLoader进行加载的,类加载器天生就负责这个职责。

Java本身给我们提供了几种类型的类加载器,启动类加载器Bootstrap ClassLoader、扩展类加载器Extension ClassLoader、应用类加载器App ClassLoader,除了上面3种,我们也可以定义我们自己的类加载器,此时我们只需要继承ClassLoader类,重写其findClass()方法即可。

当然,有时候,你以为的并不是你以为的,当我们用自定义类加载器去加载类路径ClassPath下某个class文件时,然后调用该Class对象的getClassLoader()方法时,我们会发现,加载该类的类加载器,并不是我们自定义的加载器,至于为什么?下文将会讲到。 下图是JVM类加载器机制

  • 1、启动类加载器Bootstrap ClassLoader

    启动类加载器,负责%JRE_HOME/lib/目录下的相关类文件,比如rt.jartools.jar等等,比如说我们的String类存放在rt.jar中,器加载器就是Bootstrap ClassLoader

  • 2、扩展类加载器Extension ClassLoader

    扩展类加载器,负责%JRE_HOME/lib/ext目录下的相关类文件,比如rt.jartools.jar等等

  • 3、应用类加载器App ClassLoader

    应用类加载器,负责加载应用程序类路径下的class文件。

说到类路径的问题,我们解释一下,java的类路径指的是我们配置的系统环境变量的值:CLASSPATH,但又不局限于这个值。小编先打印下我们本地的classpath的值:

.;D:\development\jdk\lib\dt.jar;D:\development\jdk\lib\tools.jar;

你会发现,这根本就不是我们用的开发工具中的目录呀,那么JVM是怎么加载到我们在eclipse上编写的类呢?答案是eclipse已经帮我们弄好了一切。

下边我们举个例子,看下String类的类加载器是什么:

System.out.println(String.class.getClassLoader());

上面这句代码输出null。为什么呢?因为如果一个类是被Bootstrap ClassLoader或者Extension ClassLoader加载时,getClassLoader()规定输出null。又因为String类存在rt.jar中,将会被Bootstrap ClassLoader加载,所以输出null

谈及类加载器,我们不得不说类的双亲委派机制,一句话总结双亲委派机制,小编总结成一句话:

如果自定义加载器P有父加载器P1,那么在加载前就将加载任务委派给其父亲P1,如果P1也存在父加载器P2,那么将加载任务委派给P2,如果最顶层的Bootstrap ClassLoader还加载不到,那么就再逆着顺序加载,直到类被加载到~~

二、ClassLoader类特性介绍

  • 1、每一个Class实例都包含一个ClassLoader引用

    因此,我们总是能通过Class对象的getClassLoader()方法获取其类加载器。当然了,就如上面说的,如果一个类是被Bootstrap ClassLoader或者Extension ClassLoader加载时,getClassLoader()规定输出null。这是一个需要我们注意的点。

  • 2、对于数组类型[]对象,它们不是由类加载器ClassLoader去进行加载的,而是Java虚拟机根据需要自动创建的。我们通过数组类型的Class对象的getClassLoader()方法返回的值跟数组里边元素所使用的的类加载器一样,但如果数组元素为原始类型int啥的,则getClassLoader()方法将返回null

    // sun.misc.Launcher$AppClassLoader@73d16e93ClassLoaderTest[] array = new ClassLoaderTest[5];System.out.println(array.getClass().getClassLoader());// nullint[] intArray = new int[3];System.out.println(intArray.getClass().getClassLoader());复制代码
  • 3、我们可以通过继承ClassLoader类,来实现自己的类加载器

/** * 自定义类加载器 * @Author jiawei huang * @Since 2019年8月7日 * @Version 1.0 */public class MyClassLoader extends ClassLoader {    // 如果我们从其他地方进行加载,我们可以指定路径    private String classpath;    public MyClassLoader(String classPath) {    	classpath = classPath;    }    public MyClassLoader() {    }    @Override    protected Class
findClass(String name) throws ClassNotFoundException { Class clazz = null; // 获取该class文件字节码数组 byte[] classData = getClassData(); if (classData != null) { // 将class的字节码数组转换成Class类的实例 clazz = defineClass(name, classData, 0, classData.length); } return clazz; } private byte[] getClassData() { byte[] bytes = null; File file = new File(classpath); if (file.exists()) { // 从文件中读取class字节数据 FileInputStream in = null; ByteArrayOutputStream out = null; try { in = new FileInputStream(file); out = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int size = 0; while ((size = in.read(buffer)) != -1) { out.write(buffer, 0, size); } } catch (IOException e) { e.printStackTrace(); } finally { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } bytes = out.toByteArray(); } return bytes; }}复制代码

假设我们在ex包路径下有个Main.java,看看我们下面的输出:

A.javaMyClassLoader loader = new MyClassLoader();Class
clazzClass = loader.loadClass("ex.Main");// 1System.out.println(clazzClass.getClassLoader());复制代码

上面1处将输出sun.misc.Launcher$AppClassLoader@73d16e93,我们可能会问,明明我是用自己的类加载器去加载ex.Main的呀,为什么却输出AppClassLoader这个类加载器呢?其实这就是双亲委派机制,MyClassLoader把加载任务给到其父加载器App ClassLoader,刚好ex.Main又处于类路径下,所以App ClassLoader加载之后就直接返回了。

  • 4、一个类整个生命周期只会被加载一次,有且仅有一次

  • 5、支持并行加载的加载器称为并行加载器,但前提是,我们必须在自定义加载器的初始化时,调用ClassLoader.registerAsParallelCapable();方法注册自己,怎么做呢?我们看看URLClassLoader的源码。

static {    // 注册自己    ClassLoader.registerAsParallelCapable();}复制代码
  • 6、在委托模型不是严格分层的情况下,自定义类加载器需要具有并行能力,否则类加载可能导致死锁,因为加载器锁在类加载过程中一直持有。

详情我们看下ClassLoaderloadClass()方法源码:

protected Class
loadClass(String name, boolean resolve) throws ClassNotFoundException{ // 这里有个锁 synchronized (getClassLoadingLock(name)) { 1、先检查这个名称的类是否已经被加载过,如果是,就不再加载了,这也印证了我们第4点说的一个类只会被加载一次 Class
c = findLoadedClass(name); // 2、如果该类没有被加载,那么就进行加载 if (c == null) { long t0 = System.nanoTime(); try { // 3、如果存在父加载器,就进行委派,这就是双亲委派机制的原理 if (parent != null) { c = parent.loadClass(name, false); } else { // 4、去`Bootstrap classloader`加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } // 5、如果3,4都没有加载到,那就执行我们自定义的classloader,这也是为什么我们要重写findClass方法的原因所在 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 stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; }}复制代码
  • 7、我们不仅仅可以从文件系统中对类进行加载,我们还可以从网络上进行class文件的加载,获取byte[],然后通过ClassLoaderdefineClass()byte[]转换成Class对象,最后通过Class.newInstance()即可转成Java对象啦~~~
ClassLoader loader = new NetworkClassLoader(host, port);Object main = loader.loadClass("Main", true).newInstance();复制代码

三、SpringBoot中,构造器为什么要加上ClassLoader参数?

因为初始化过程中,load()方法需要从不同的地方去加载类文件。

protected void load(ApplicationContext context, Object[] sources) {    if (logger.isDebugEnabled()) {    	logger.debug(    			"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));    }    BeanDefinitionLoader loader = createBeanDefinitionLoader(    		getBeanDefinitionRegistry(context), sources);    if (this.beanNameGenerator != null) {    	loader.setBeanNameGenerator(this.beanNameGenerator);    }    if (this.resourceLoader != null) {    	loader.setResourceLoader(this.resourceLoader);    }    if (this.environment != null) {    	loader.setEnvironment(this.environment);    }    loader.load();}复制代码

转载于:https://juejin.im/post/5d4a89a36fb9a06ae61aa1c8

你可能感兴趣的文章
ArcEngine实现坐标转换和投影(转载)
查看>>
solr集群SolrCloud(solr+zookeeper)windows搭建
查看>>
内核开发基础3——Linux内核配置与编译
查看>>
BUPT复试专题—中序遍历序列(2013)
查看>>
【常见Web应用安全问题】---7、CRLF injection
查看>>
php7.2.1 安装
查看>>
用winrar解压时提示无法设置安全数据 拒绝访问的解决方法
查看>>
诡异的数学,数字问题 - leetcode
查看>>
交换输出
查看>>
设计模式-策略模式&状态模式&访问者模式
查看>>
python学习第三十三节(IO模型)
查看>>
linux pci 驱动小结
查看>>
BZOJ2744: [HEOI2012]朋友圈
查看>>
设计模式之抽象工厂模式
查看>>
大整数相关的几道题
查看>>
利用表格实现大图轮播
查看>>
SpringBoot集成jsp
查看>>
30分钟学会如何使用Apache Shiro
查看>>
asp.net部署时加密config文件
查看>>
想开个网店的。。学习一下vancl的分析
查看>>