我的编程空间,编程开发者的网络收藏夹
学习永远不晚

java类加载器ClassLoader详解

短信预约 信息系统项目管理师 报名、考试、查分时间动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

java类加载器ClassLoader详解

获得ClassLoader的途径

获得当前类的ClassLoader

clazz.getClassLoader()

获得当前线程上下文的ClassLoader

Thread.currentThread().getContextClassLoader();

获得系统的ClassLoader

ClassLoader.getSystemClassLoader()

获得调用者的ClassLoader

DriverManager.getCallerClassLoader

ClassLoader源码解析

(推荐学习:Java视频教程)

概述

类加载器是用于加载类的对象,ClassLoader是一个抽象类。如果我们给定了一个类的二进制名称,类加载器应尝试去定位或生成构成定义类的数据。一种典型的策略是将给定的二进制名称转换为文件名,然后去文件系统中读取这个文件名所对应的class文件。

每个Class对象都会包含一个定义它的ClassLoader的一个引用。

数组类的Class对象,不是由类加载器去创建的,而是在Java运行期JVM根据需要自动创建的。对于数组类的类加载器来说,是通过Class.getClassLoader()返回的,与数组当中元素类型的类加载器是一样的;如果数组当中的元素类型是一个原生类型,数组类是没有类加载器的【代码一】。

应用实现了ClassLoader的子类是为了扩展JVM动态加载类的方式。

类加载器典型情况下时可以被安全管理器所使用去标识安全域问题。

ClassLoader类使用了委托模型来寻找类和资源,ClassLoader的每一个实例都会有一个与之关联的父ClassLoader,当ClassLoader被要求寻找一个类或者资源的时候,ClassLoader实例在自身尝试寻找类或者资源之前会委托它的父类加载器去完成。虚拟机内建的类加载器,称之为启动类加载器,是没有父加载器的,但是可以作为一个类加载器的父类加载器【双亲委托机制】。

支持并发类加载的类加载器叫做并行类加载器,要求在初始化期间通过ClassLoader.registerAsParallelCapable 方法注册自身,ClassLoader类默认被注册为可以并行,但是如果它的子类也是并行加载的话需要单独去注册子类。

在委托模型不是严格的层次化的环境下,类加载器需要并行,否则类加载会导致死锁,因为加载器的锁在类加载过程中是一直被持有的。

通常情况下,Java虚拟机以平台相关的形式从本地的文件系统中加载类,比如在UNIX系统,虚拟机从CLASSPATH环境所定义的目录加载类。
然而,有些类并不是来自于文件;它们是从其它来源得到的,比如网络,或者是由应用本身构建【动态代理】。定义类(defineClass )方法会将字节数组转换为Class的实例,这个新定义类的实例可以由Class.newInstance创建。

由类加载器创建的对象的方法和构造方法可能引用其它的类,为了确定被引用的类,Java虚拟机会调用最初创建类的类加载器的loadClass方法。

二进制名称:以字符串参数的形式向CalssLoader提供的任意一个类名,必须是一个二进制的名称,包含以下四种情况

  • "java.lang.String" 正常类
  • "javax.swing.JSpinner$DefaultEditor" 内部类
  • "java.security.KeyStore(Builder)FileBuilder$1" KeyStore的内部类Builder的内部类FileBuilder的第一个匿名内部类
  • "java.net.URLClassLoader$3$1" URLClassLoader类的第三个匿名内部类的第一个匿名内部类

代码一:

public class Test12 {
    public static void main(String[] args) {
        String[] strings = new String[6];
        System.out.println(strings.getClass().getClassLoader());
        // 运行结果:null

        Test12[] test12s = new Test12[1];
        System.out.println(test12s.getClass().getClassLoader());
        // 运行结果:sun.misc.Launcher$AppClassLoader@18b4aac2

        int[] ints = new int[2];
        System.out.println(ints.getClass().getClassLoader());
        // 运行结果:null
    }
}

loadClass方法

loadClass的源码如下, loadClass方法加载拥有指定的二进制名称的Class,默认按照如下顺序寻找类:

a)调用findLoadedClass(String)检查这个类是否被加载

b)调用父类加载器的loadClass方法,如果父类加载器为null,就会调用启动类加载器

c)调用findClass(String)方法寻找

使用上述步骤如果类被找到且resolve为true,就会去调用resolveClass(Class)方法

protected Class loadClass(String name, boolean resolve)
  throws ClassNotFoundException
{
  synchronized (getClassLoadingLock(name)) {
      // First, check if the class has already been loaded
      Class 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 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;
  }
}

findClass方法

findClass的源码如下,findClass寻找拥有指定二进制名称的类,JVM鼓励我们重写此方法,需要自定义加载器遵循双亲委托机制,该方法会在检查完父类加载器之后被loadClass方法调用,默认返回ClassNotFoundException异常。

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

defineClass方法

defineClass的源码如下,defineClass方法将一个字节数组转换为Class的实例。

protected final Class defineClass(String name, byte[] b, int off, int len,
                                     ProtectionDomain protectionDomain)
    throws ClassFormatError
{
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}

自定义类加载器


public class ClassLoaderTest extends ClassLoader {
    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
       Class clazz = loader.loadClass("classloader.Test01");
        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
    //------------------------------以上为测试代码---------------------------------

    
    private String classLoaderName;

    
    private String fileExtension = ".class";

    
    private ClassLoaderTest(String classLoaderName) {
        // 将系统类加载器当做该类加载器的父加载器
        super();
        this.classLoaderName = classLoaderName;
    }

    
    private ClassLoaderTest(ClassLoader parent, String classLoaderName) {
        // 显示指定该类加载器的父加载器
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    
    @Override
    protected Class findClass(String className) throws ClassNotFoundException {
        byte[] data = this.loadClassData(className);
        
        return this.defineClass(className, data, 0 , data.length);
    }

    
    private byte[] loadClassData(String className) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data;
        ByteArrayOutputStream byteArrayOutputStream = null;

        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            inputStream = new FileInputStream(new File(className + this.fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();

            int ch;
            while (-1 != (ch = inputStream.read())) {
                byteArrayOutputStream.write(ch);
            }

            data = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }
}

以上是一段自定义类加载器的代码,我们执行这段代码

classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2

可以看见,这段代码中进行类加载的类加载器还是系统类加载器(AppClassLoader)。这是因为jvm的双亲委托机制造成的,private ClassLoaderTest(String classLoaderName)将系统类加载器当做我们自定义类加载器的父加载器,jvm的双亲委托机制使自定义类加载器委托系统类加载器完成加载。

改造以下代码,添加一个path属性用来指定类加载位置:

public class ClassLoaderTest extends ClassLoader {
    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
        loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
        Class clazz = loader.loadClass("classloader.Test01");
        System.out.println("class:" + clazz);

        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
    //------------------------------以上为测试代码---------------------------------

    
    private String path;

    ......
    
    
    private byte[] loadClassData(String className) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data;
        ByteArrayOutputStream byteArrayOutputStream = null;

        className = className.replace(".", "/");

        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            inputStream = new FileInputStream(new File(this.path + className + this.fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();

            int ch;
            while (-1 != (ch = inputStream.read())) {
                byteArrayOutputStream.write(ch);
            }

            data = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public void setPath(String path) {
        this.path = path;
    }
}

运行一下

class:class classloader.Test01
classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2

修改一下测试代码,并删除工程下的Test01.class文件

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
   loader.setPath("/home/fanxuan/桌面/");
    Class clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz);

    Object object = clazz.newInstance();
    System.out.println(object);
    System.out.println(object.getClass().getClassLoader());
}

运行一下

class:class classloader.Test01
classloader.Test01@135fbaa4
classloader.ClassLoaderTest@7f31245a

分析

改造后的两块代码,第一块代码中加载类的是系统类加载器AppClassLoader,第二块代码中加载类的是自定义类加载器ClassLoaderTest。是因为ClassLoaderTest会委托他的父加载器AppClassLoader加载class,第一块代码的path直接是工程下,AppClassLoader可以加载到,而第二块代码的path在桌面目录下,所以AppClassLoader无法加载到,然后ClassLoaderTest自身尝试加载并成功加载到。如果第二块代码工程目录下的Test01.class文件没有被删除,那么依然是AppClassLoader加载。

再来测试一块代码

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
    Class clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest("loader");
    loader2.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
    Class clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

结果显而易见,类由系统类加载器加载,并且clazz和clazz2是相同的。

class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2
class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2

再改造一下

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/桌面/");
    Class clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest("loader2");
    loader2.setPath("/home/fanxuan/桌面/");
    Class clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

运行结果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:621009875
classloader.ClassLoaderTest@45ee12a7

ClassLoaderTest是显而易见,但是clazz和clazz2是不同的,这是因为类加载器的命名空间的原因。

我们可以通过设置父类加载器来让loader和loader2处于同一命名空间

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/桌面/");
    Class clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest(loader, "loader2");
    loader2.setPath("/home/fanxuan/桌面/");
    Class clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

运行结果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:325040804
classloader.ClassLoaderTest@7f31245a

扩展:命名空间

每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父加载器所加载的类组成

在同一命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类

在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类

编程界网,大量的免费Java入门教程,欢迎在线学习!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

java类加载器ClassLoader详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

java类加载器ClassLoader详解

获得ClassLoader的途径1. 获得当前类的ClassLoaderclazz.getClassLoader()2. 获得当前线程上下文的ClassLoaderThread.currentThread().getContextClassLoader();3
java类加载器ClassLoader详解
2019-01-28

Java类加载器ClassLoader的使用详解

类加载器负责读取Java字节代码,并转换成java.lang.Class类的一个实例的代码模块。本文主要和大家聊聊JVM类加载器ClassLoader的使用,需要的可以了解一下
2022-12-19

JVM类加载器之ClassLoader的使用详解

类加载器负责读取Java字节代码,并转换成java.lang.Class类的一个实例的代码模块。本文主要和大家聊聊JVM类加载器ClassLoader的使用,需要的可以了解一下
2022-11-13

Java中ClassLoader类加载的示例分析

这篇文章主要为大家展示了“Java中ClassLoader类加载的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Java中ClassLoader类加载的示例分析”这篇文章吧。双亲委派模型
2023-05-30

classloader类加载器基于java类的加载方式的示例分析

这篇文章主要介绍classloader类加载器基于java类的加载方式的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!基础概念Classloader 类加载器,用来加载 Java 类到 Java 虚拟机中。与
2023-05-31

Java类加载机制及类加载器详解

熟悉java开发的同学都知道,我们日常所写的代码都被保存到.java文件中。这些\".java\"文件经过Java编译器编译成拓展名为\".class\"的文件,\".class\"文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟

【Java 基础篇】Java类加载器详解

文章目录 导言一、类加载器的概念二、类加载器的工作原理三、类加载器的使用示例四、自定义类加载器总结 导言 Java类加载器(Class Loader)是Java虚拟机(JVM)的一部分,负责将类的字节码加载到内存中,并将其转
2023-08-23

实现自定义ClassLoader:扩展Java类加载机制

自定义ClassLoader是扩展Java类加载机制的一种方式,通过继承ClassLoader并重写其中的方法,可以实现对类加载过程的自定义控制。
Java开发2024-11-30

Java类加载过程与类加载器详细介绍

这篇文章主要介绍了Java类加载过程与类加载器详细介绍,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下
2022-11-13

Java自定义类加载器实现类隔离详解

由于每种组件的不同版本所依赖的jar包不同,我们可以借鉴tomcat的实现方式,通过自定义类加载器打破双亲委派机制来实现类隔离,从而达到操作多组件多版本的目的。本文就来和大家详细聊聊实现方法
2023-03-02

怎么在Android中利用ClassLoader对类进行加载

本篇文章给大家分享的是有关怎么在Android中利用ClassLoader对类进行加载,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Java的类加载设计了一套双亲代理的模式,使
2023-05-31

怎么解析Java类加载器

这篇文章给大家介绍怎么解析Java类加载器,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Jav
2023-06-02

Android动态加载之ClassLoader加载和插件热修复的机制原理详解

ClassLoader类加载,是动态加载机制及现在火热的插件化机制中很基础但同时又很重要的知识点;今天我们就来讲解下.

深入理解java之类加载器

一、类与类加载器类加载器:实现加载阶段的第一步,通过一个类的全限定名来将这个类的二进制字节流加载进jvm。类与类加载器:任意一个类唯一性都是由它本身和加载它的类加载器确定,两个类是否相等在它们是由同一个类加载器加载的前提下。jvm虚拟机中包括两种类加载器:一种
深入理解java之类加载器
2014-12-01

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录