侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

java类加载器ClassLoader详解

Java  /  管理员 发布于 8年前   181

获得ClassLoader的途径

1. 获得当前类的ClassLoader

clazz.getClassLoader()

2. 获得当前线程上下文的ClassLoader

Thread.currentThread().getContextClassLoader();

3. 获得系统的ClassLoader

ClassLoader.getSystemClassLoader()

4. 获得调用者的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;}

自定义类加载器

/** * 继承了ClassLoader,这是一个自定义的类加载器 * @author 夜的那种黑丶 */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";    /**     * 创建一个类加载器对象,将系统类加载器当做该类加载器的父加载器     * @param classLoaderName 类加载器名称     */    private ClassLoaderTest(String classLoaderName) {        // 将系统类加载器当做该类加载器的父加载器        super();        this.classLoaderName = classLoaderName;    }    /**     * 创建一个类加载器对象,显示指定该类加载器的父加载器     * 前提是需要有一个类加载器作为父加载器     * @param parent 父加载器     * @param classLoaderName 类加载器名称     */    private ClassLoaderTest(ClassLoader parent, String classLoaderName) {        // 显示指定该类加载器的父加载器        super(parent);        this.classLoaderName = classLoaderName;    }    /**     * 寻找拥有指定二进制名称的类,重写ClassLoader类的同名方法,需要自定义加载器遵循双亲委托机制     * 该方法会在检查完父类加载器之后被loadClass方法调用     * 默认返回ClassNotFoundException异常     * @param className 类名     * @return Class的实例     * @throws ClassNotFoundException 如果类不能被找到,抛出此异常     */    @Override    protected Class<?> findClass(String className) throws ClassNotFoundException {        byte[] data = this.loadClassData(className);        /*         * 通过defineClass方法将字节数组转换为Class         * defineClass:将一个字节数组转换为Class的实例,在使用这个Class之前必须要被解析         */        return this.defineClass(className, data, 0 , data.length);    }    /**     * io操作,根据类名找到对应文件,返回class文件的二进制信息     * @param className 类名     * @return class文件的二进制信息     * @throws ClassNotFoundException 如果类不能被找到,抛出此异常     */    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;    }}

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

[email protected]$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;    ......        /**     * io操作,根据类名找到对应文件,返回class文件的二进制信息     * @param className 类名     * @return class文件的二进制信息     * @throws ClassNotFoundException 如果类不能被找到,抛出此异常     */    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 [email protected]$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.Test01classloader.Test01@135fbaa4classloader.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:2133927002sun.misc.Launcher$AppClassLoader@18b4aac2class:2133927002sun.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:325040804classloader.ClassLoaderTest@7f31245aclass:621009875classloader.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:325040804classloader.ClassLoaderTest@7f31245aclass:325040804classloader.ClassLoaderTest@7f31245a

扩展:命名空间

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

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

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

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

以上就是java类加载器ClassLoader详解的详细内容,更多请关注其它相关文章!


  • 上一条:
    java中enum(枚举)的用法详解
    下一条:
    java方法中的构造方法与普通方法的区别
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在java中实现的脱敏工具类代码示例分享(0个评论)
    • zookeeper安装流程步骤(0个评论)
    • 在java中你背的“八股文”可能已经过时了(2个评论)
    • 在php8.0+版本中使用属性来增加值代码示例(3个评论)
    • java 正则表达式基础,实例学习资料收集大全 原创(0个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-11
    • 2018-03
    • 2020-03
    • 2023-05
    • 2023-11
    • 2024-01
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客