Java类加载器层次结构原理解析

类加载器的层次结构:

引导类加载器(bootstrap class loader)

  用来加载java的核心库(JAVA_HOME/jre/lib/rt.jar,或sun.boot.class.path路径下的内容),是用原生代码来实现的(C实现的),并不继承自java.lang.ClassLoader。

  加载扩展类和应用程序类加载器,并指定它们的父类加载器。

扩展类加载器(extensions class loader)

  用来加载java的扩展库(JAVA_HOME/jre/lib/ext/*.jar,或java.ext.dirs路径下的内容)java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载java类。

  有sun.miscLauncher$ExtClassLoader实现,继承自java.lang.ClassLoader

应用程序类加载器(application class loader)

  它根据java应用的类路径(classpath,java.class.path路径)来加载指定路径的类,一般来说,java应用的类都是由它来完成加载的

  由sun.misc.Launcher$AppClassLoader实现,继承自java.lang.ClassLoader

自定义类加载器

  开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

说明:在java中由于类的加载采用的是双亲委托机制,上面几种类加载器是父子关系,其中引导类加载器为基础。

ClassLoader类介绍

作用:

  java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个java类,即java.lang.Class类的一个实例。

  除此之外,ClassLoader还负责加载java应用所需的资源文件,如图像文件和配置文件等。

相关方法:

  • getParent()  返回该类加载器的父类加载器
  • loadClass(String name)  加载名称为name的类,返回的结果是java.lang.Class类的实例
  • findClass(String name)  查找名称为name的类,返回的结果是java.lang.Class类的实例
  • findLoadedClass(String name)  查找名称为name的已经被加载过的类,返回的结果是java.lang.Class类的实例
  • defineClass(String name,byte[] b,int off,int len)  把字节数组b中的内容转换成java类,返回的结果是java.lang.Class类的实例。这个方法被声明为final的。
  • resolveClass(Class<?> c)  链接指定的java类。

代码测试类加载器:

public class Demo02 {
  public static void main(String[] args) {
    System.out.println(ClassLoader.getSystemClassLoader());
    System.out.println(ClassLoader.getSystemClassLoader().getParent());;
    System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());;
  }
}

输出:

sun.misc.Launcher$AppClassLoader@1016632

sun.misc.Launcher$ExtClassLoader@dc6a77

null

依次为应用加载器、扩展加载器和引导加载器(但是引导加载为原生代码所写,因此获取不到,为null)。

类加载器的代理模式:

代理模式:交给其他加载器来加载指定的类。

双亲委托机制:

  就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,以此追溯,直到最高的爷爷辈的,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

  双亲委托机制是为了保证java核心库的类型安全(这种机制就保证不会出现用户自己能定义java.lang.Object类的情况)。

  类加载器除了用于加载类,也是安全的最基本的屏障。

双亲委托机制是代理模式的一种:

  并不是所有的类加载器都采用双亲委托机制。

  tomcat服务器类加载器也使用代理模式,所不同的是它是首先尝试自己去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。

自定义类加载器的流程:

  继承:java.lang.ClassLoader

  首先检查请求的类型是否已经被这个类装载器装载到命名空间中,如果已经装载,则返回

  委派类将加载请求给父类加载器,如果父类加载器能够完成,则返回父类加载器加载的Class实例

  调用本类加载器的findClass()方法,师徒获取对应的字节码,如果获取得到,则调用defineClass()导入类型到方法区;如果获取不到对应的字节码或者其它原因失败,则返回异常给loadClass(),loadClass()转抛异常,终止加载过程

  注:被两个加载器加载的同一个类,Jvm不认为是相同的类。

示例代码如下:

package com.test;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * 自定义文件系统加载器
 * @author We.lxk
 *
 */
public class FileSystemClassLoader extends ClassLoader{
  private String rootDir;

  public FileSystemClassLoader(String rootDir) {
    this.rootDir = rootDir;
  }

  private byte[] getClassData(String classname){    //com.test.User -> rootDir/com/test/User
    String path = rootDir +"/"+classname.replace(".", "/")+".class";
    //IOUtils 可以使用它将读取的流数据转换为字节数组
    InputStream is = null;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try{
      is = new FileInputStream(path);

      byte[] buffer = new byte[1024];
      int temp = 0;
      while((temp=is.read(buffer))!=-1){
        baos.write(buffer, 0, temp);
      }
      return baos.toByteArray();
    }catch(Exception e){
      e.printStackTrace();
      return null;
    }finally{
        try {
          if(is!=null)
          is.close();
        } catch (IOException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
        try {
          if(baos!=null)
          baos.close();
        } catch (IOException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
    }
  }

  @Override
  public Class<?> loadClass(String name) throws ClassNotFoundException {
    Class<?> c = findLoadedClass(name);

    //应该先查询有没有加载过这个类。已经加载,则直接返回加载好的类。
    if(c!=null){
      return c;
    }else{
      ClassLoader parent = this.getParent();
      try{
        //System.out.println("hello");
        c = parent.loadClass(name);          //委派给父类加载
      }catch(Exception e){
        //e.printStackTrace();
      }
      if(c!=null){
        return c;
      }else{
        byte[] classData = getClassData(name);
        if(classData==null){
          throw new ClassNotFoundException();
        }else{
          c = defineClass(name, classData, 0, classData.length);
        }
      }
    }
    return c;
  }
}

测试代码:

package com.test;

/**
 *  测试自定义的FileSystemClassLoader
 * @author We.lxk
 *
 */
public class Demo03 {
  public static void main(String[] args) throws Exception {
    FileSystemClassLoader loader = new FileSystemClassLoader("D:/myJava");
    FileSystemClassLoader loader2 = new FileSystemClassLoader("D:/myJava");

    Class<?> c = loader.loadClass("com.test.Demos");
    Class<?> c2 = loader.loadClass("com.test.Demos");
    Class<?> c3 = loader2.loadClass("com.test.Demos");

    Class<?> c4 = loader2.loadClass("java.lang.String");
    Class<?> c5 = loader.loadClass("com.test.Demo");

    System.out.println(c.hashCode()+" "+c.getClassLoader());
    System.out.println(c2.hashCode()+" "+c2.getClassLoader());
    System.out.println(c3.hashCode()+" "+c3.getClassLoader());
    System.out.println(c4.hashCode()+" "+c4.getClassLoader());
    System.out.println(c5.hashCode()+" "+c5.getClassLoader());
    //System.out.println(.getClassLoader());
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java使用Cipher类实现加密的过程详解

    一.先看一个简单加密,解密实现 1.1 加密 /** * content: 加密内容 * slatKey: 加密的盐,16位字符串 * vectorKey: 加密的向量,16位字符串 */ public String encrypt(String content, String slatKey, String vectorKey) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

  • java并发编程专题(十)----(JUC原子类)基本类型详解

    这一节我们先来看一下基本类型: AtomicInteger, AtomicLong, AtomicBoolean.AtomicInteger和AtomicLong的使用方法差不多,AtomicBoolean因为比较简单所以方法比前两个都少,那我们这节主要挑AtomicLong来说,会使用一个,其余的大同小异. 1.原子操作与一般操作异同 我们在说原子操作之前为了有个对比为什么需要这些原子类而不是普通的基本数据类型就能满足我们的使用要求,那就不得不提原子操作不同的地方. 当你在操作一个普通变量时,

  • Java枚举类型在switch语句正确使用方法详解

    很多人也许会尝试写下这样的代码: ResultStructureEnum type = ResultStructureEnum.valueOf(userType); switch (type) { case ResultStructureEnum.STUDENT: ... break; case ResultStructureEnum.TEACHER: ... break; case ResultStructureEnum.PARENT: ... break; ... } # 这样编译不会通过,

  • Java lastIndexOf类使用方法原理解析

    lastIndexOf 在字符串中根据搜索条件来返回其在字符串中的位置,空格也计数,如果字符串中没有这样的字符,返回-1. 其方法主要有4个: lastIndexOf(int ch) ,返回指定字符在此字符串中最后一次出现处的索引. lastIndexOf(int ch , int fromIndex) ,返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向搜索. lastIndexOf(String str),返回指定子字符串在此字符串中最右边出现处的索引. lastInd

  • Java object类及正则表达式原理解析

    equals方法 equals方法,用于比较两个对象是否相同 /* 描述人这个类,并定义功能根据年龄判断是否是同龄人 由于要根据指定类的属性进行比较,这时只要覆盖Object中的equals方法 在方法体中根据类的属性值进行比较 */ class Person extends Object{ int age ; //复写父类的equals方法,实现自己的比较方式 public boolean equals(Object obj) { //判断当前调用equals方法的对象和传递进来的对象是否是同

  • java并发编程专题(十一)----(JUC原子类)数组类型详解

    上一节我们介绍过三个基本类型的原子类,这次我们来看一下数组类型: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray.其中前两个的使用方式差不多,AtomicReferenceArray因为他的参数为引用数组,所以跟前两个的使用方式有所不同. 1.AtomicLongArray介绍 对于AtomicLongArray, AtomicIntegerArray我们还是只介绍一个,另一个使用方式大同小异. 我们先来看看AtomicLong

  • JAVA匿名内部类语法分析及实例详解

    1.前言 匿名内部类在我们JAVA程序员的日常工作中经常要用到,但是很多时候也只是照本宣科地用,虽然也在用,但往往忽略了以下几点:为什么能这么用?匿名内部类的语法是怎样的?有哪些限制?因此,最近,我在完成了手头的开发任务后,查阅了一下JAVA官方文档,将匿名内部类的使用进行了一下总结,案例也摘自官方文档.感兴趣的可以查阅官方文档(https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html). 2.匿名内部类

  • 浅析Java Scanner 类的用法

    java.util.Scanner 是 Java5 的新特征,我们可以通过 Scanner 类来获取用户的输入. 下面是创建 Scanner 对象的基本语法: Scanner s = new Scanner(System.in); 接下来我们演示一个最简单的数据输入,并通过 Scanner 类的 next() 与 nextLine() 方法获取输入的字符串,在读取前我们一般需要 使用 hasNext 与 hasNextLine 判断是否还有输入的数据: 使用 next 方法: import ja

  • Java类加载器层次结构原理解析

    类加载器的层次结构: 引导类加载器(bootstrap class loader) 用来加载java的核心库(JAVA_HOME/jre/lib/rt.jar,或sun.boot.class.path路径下的内容),是用原生代码来实现的(C实现的),并不继承自java.lang.ClassLoader. 加载扩展类和应用程序类加载器,并指定它们的父类加载器. 扩展类加载器(extensions class loader) 用来加载java的扩展库(JAVA_HOME/jre/lib/ext/*.

  • Java类加载器ClassLoader用法解析

    这篇文章主要介绍了Java类加载器ClassLoader用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 正文 当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载.连接.初始化3个步骤来对该类进行初始化.如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化. 一.类加载过程 1.加载 加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说

  • 源码解析Java类加载器

    参考内容: 深入理解Java虚拟机(JVM高级特性与最佳实践) --周志明老师 尚硅谷深入理解JVM教学视频--宋红康老师 我们都知道Java的类加载器结构为下图所示(JDK8及之前,JDK9进行了模块化): 关于三层类加载器.双亲委派机制,本文不再板书,读者可自行百度. 那么在JDK的源码中,三层结构的具体实现是怎么样的呢? Bootstrap ClassLoader(引导类加载器) 引导类加载器是由C++实现的,并非Java代码实现,所以在Java代码中是无法获取到该类加载器的. 一般大家都

  • Java类加载器和类加载机制实例分析

    本文实例讲述了Java类加载器和类加载机制.分享给大家供大家参考,具体如下: 一 点睛 1 类加载器负责将.class文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之生成对应的java.lang.Class对象. 2 当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构: Bootstrap ClassLoader:根类加载器. Extension ClassLoader:扩展类加载器. System ClassLoader:系统类加载器. 3 JVM的类加载机制主要有如下三种

  • Java多态中动态绑定原理解析

    这篇文章主要介绍了Java多态中动态绑定原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 多态是面向对象程序设计非常重要的特性,它让程序拥有 更好的可读性和可扩展性. 发生在继承关系中. 需要子类重写父类的方法. 父类类型的引用指向子类类型的对象. 自始至终,多态都是对于方法而言,对于类中的成员变量,没有多态的说法. 一个基类的引用变量接收不同子类的对象将会调用子类对应的方法,这其实就是动态绑定的过程.在理解动态绑定之前,先补充一些概念.

  • 浅谈JAVA 类加载器

    类加载机制 类加载器负责加载所有的类,系统为所有被载入内存中的类生成一个 java.lang.Class 实例.一旦一个类被载入 JVM 中,同个类就不会被再次载入了.现在的问题是,怎么样才算"同一个类"? 正如一个对象有一个唯一的标识一样,一个载入 JVM 中的类也有一个唯一的标识.在 Java 中,一个类用其全限定类名(包括包名和类名)作为标识:但在 JVM 中,一个类用其全限定类名和其类加载器作为唯一标识.例如,如果在 pg 的包中有一个名为 Person 的类,被类加载器 Cl

  • GC参考手册二java中垃圾回收原理解析

    内存碎片整理 每次执行清除(sweeping), JVM 都必须保证不可达对象占用的内存能被回收重用.但这(最终)有可能会产生内存碎片(类似于磁盘碎片), 进而引发两个问题: 写入操作越来越耗时, 因为寻找一块足够大的空闲内存会变得非常麻烦. 在创建新对象时, JVM在连续的块中分配内存.如果碎片问题很严重, 直至没有空闲片段能存放下新创建的对象,就会发生内存分配错误(allocation error). 要避免这类问题,JVM 必须确保碎片问题不失控.因此在垃圾收集过程中, 不仅仅是标记和清除

  • 详解Java类加载器与双亲委派机制

    目录 引子 了解.class文件 类加载的过程 类加载器 与 双亲委派机制 ClassLoader 自定义类加载器 编写一个自定义的类加载器 为什么我们这边要打破双亲委派机制 自定义类加载器时,如何打破双亲委派机制 SPI机制 与 线程上下文类加载器 JDBC Tomcat SpringBoot Starter 尾语 引子 大家想必都有过平时开发springboot 项目的时候稍微改动一点代码,就得重启,就很烦 网上一般介绍 2种方式 spring-boot-devtools,或者通过JRebe

  • Java类加载器ClassLoader源码层面分析讲解

    目录 Launcher 源码 AppClassLoader 源码 ExtClassLoader 源码 ClassLoader 源码 总结 最终总结一下 Launcher 源码 sun.misc.Launcher类是java 虚拟机的入口,在启动 java应用 的时候会首先创建Launcher.在初始化Launcher对象的时候会创建一个ExtClassLoader拓展程序加载器 和 AppClassLoader应用程序类加载器(这俩鬼东西好像只是加载类的路径不一样而已),然后由这俩类加载器去加载

  • Java设计模式模板方法(Template)原理解析

    这篇文章主要介绍了Java设计模式模板方法(Template)原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 前言: 我们在开发中有很多固定的流程,这些流程有很多步凑是固定的,比如JDBC中获取连接,关闭连接这些流程是固定不变的,变动的只有设置参数,解析结果集这些是根据不同的实体对象"来做调整",针对这种拥有固定算法流程,其中有固定的步凑,存在不固定的步凑的情况下就诞生了模板方法模式. 模板方法模式(Template)定义:

随机推荐