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

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

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

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

看不清楚,换张图片

免费获取短信验证码

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

类加载器

概述

类加载器负责读取Java字节代码,并转换成java.lang.Class类的一个实例的代码模块。

类加载器除了用于加载类外,还可用于确定类在Java虚拟机中的唯一性。

任意一个类,都由加载它的类加载器和这个类本身一同确定其在 Java 虚拟机中的唯一性,每一个类加载器,都有一个独立的类名称空间,而不同类加载器中是允许同名(指全限定名相同)类存在的。

比较两个类是否“相等”,前提是这两个类由同一个类加载器加载,否则,即使这两个类来源于同一个Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那么这两个类就必定不相等。

这里“相等”是指:类的Class对象的equals()方法、isInstance()方法的返回结果,使用instanceof关键字做对象所属关系判定等情况。

加载器的种类

1.启动类加载器:Bootstrap ClassLoader

最顶层的加载类,由 C++实现,负责加载%JAVA_HOME%/lib目录下的jar包和类或者被 -Xbootclasspath参数指定的路径中的所有类。

2.拓展类加载器:Extension ClassLoader

负责加载java平台中扩展功能的一些jar包,如加载%JRE_HOME%/lib/ext目录下的jar包和类,或-Djava.ext.dirs所指定的路径下的jar包。

3.系统类加载器/应用程序加载器:App ClassLoader

负责加载当前应用classpath中指定的jar包及-Djava.class.path所指定目录下的类和jar包。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

4.自定义类加载器:Custom ClassLoader

通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader

验证不同加载器

每个类加载都有一个父类加载器,可以通过程序来验证

    public static void main(String[] args) {
        // App ClassLoader
        System.out.println(new User().getClass().getClassLoader());
        // Ext ClassLoader
        System.out.println(new User().getClass().getClassLoader().getParent());
        // Bootstrap ClassLoader
        System.out.println(new User().getClass().getClassLoader().getParent().getParent());
        // Bootstrap ClassLoader
        System.out.println(new String().getClass().getClassLoader());
    }

AppClassLoader的父类加载器为ExtClassLoader, ExtClassLoader的父类加载器为 null,null 并不代表ExtClassLoader没有父类加载器,而是 BootstrapClassLoader 。

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@5fdef03a
null
null

核心方法

查看类ClassLoader的loadClass方法

 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 {
                	// 父加载器不为空,调用父加载器loadClass()方法处理
                    if (parent != null) {
                    	// 让上一层加载器进行加载
                        c = parent.loadClass(name, false);
                    } else {
                    	// 父加载器为空,使用启动类加载器 BootstrapClassLoader 加载
                        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();
                    // 调用此类加载器所实现的findClass方法进行加载
                    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方法是当字节码加载到内存后进行链接操作,对文件格式和字节码验证,并为 static 字段分配空间并初始化,符号引用转为直接引用,访问控制,方法覆盖等
                resolveClass(c);
            }
            return c;
        }
    }

JVM类加载机制的三种方式

全盘负责

当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入

注意:

系统类加载器AppClassLoader加载入口类(含有main方法的类)时,会把main方法所依赖的类及引用的类也载入。只是调用了ClassLoader.loadClass(name)方法,并没有真正定义类。真正加载class字节码文件生成Class对象由双亲委派机制完成。

父类委托、双亲委派

父类委托即双亲委派,双亲委派模型是描述类加载器之间的层次关系。它要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。父子关系一般不会以继承的关系实现,而是以组合关系来复用父加载器的代码。

双亲委派模型是指:子类加载器如果没有加载过该目标类,就先委托父类加载器加载该目标类,只有在父类加载器找不到字节码文件的情况下才从自己的类路径中查找并装载目标类。

双亲委派模型的好处

保证Java程序的稳定运行,避免类的重复加载:JVM区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类

保证Java核心API不被篡改:如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,如编写一个称为java.lang.Object 类,程序运行时,系统就会出现多个不同的Object类。反之使用双亲委派模型:无论使用哪个类加载器加载,最终都会委派给最顶端的启动类加载器加载,从而使得不同加载器加载的Object类都是同一个。

双亲委派机制加载Class的具体过程:

1. ClassLoader先判断该Class是否已加载,如果已加载,则返回Class对象,如果没有则委托给父类加载器

2. 父类加载器判断是否加载过该Class,如果已加载,则返回Class对象,如果没有则委托给祖父类加载器

3. 依此类推,直到始祖类加载器(引用类加载器)

4. 始祖类加载器判断是否加载过该Class,如果已加载,则返回Class对象

如果没有则尝试从其对应的类路径下寻找class字节码文件并载入

如果载入成功,则返回Class对象;如果载入失败,则委托给始祖类加载器的子类加载器

5. 始祖类加载器的子类加载器尝试从其对应的类路径下寻找class字节码文件并载入

如果载入成功,则返回Class对象;如果载入失败,则委托给始祖类加载器的孙类加载器

6. 依此类推,直到源ClassLoader

7. 源ClassLoader尝试从其对应的类路径下寻找class字节码文件并载入

如果载入成功,则返回Class对象;如果载入失败,源ClassLoader不会再委托其子类加载器,而是抛出异常

注意:

双亲委派机制是Java推荐的机制,并不是强制的机制。可以继承java.lang.ClassLoader类,实现自己的类加载器。如果想保持双亲委派模型,应该重写findClass(name)方法;如果想破坏双亲委派模型,可以重写loadClass(name)方法。

缓存机制

缓存机制将会保证所有加载过的Class都将在内存中缓存,当程序中需要使用某个Class时,类加载器先从内存的缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。

对于一个类加载器实例来说,相同全名的类只加载一次,即loadClass方法不会被重复调用。因此,这就是为什么修改Class后,必须重启JVM,程序的修改才会生效的原因。

JDK8使用的是直接内存,所以会用到直接内存进行缓存。因此,类变量为什么只会被初始化一次的原因。

打破双亲委派

在加载类的时候,会一级一级向上委托,判断是否已经加载,从自定义类加载器 --> 应用类加载器 --> 扩展类加载器 --> 启动类加载器,如果到最后都没有加载这个类,则回去加载自己的类。

双亲委派模型并不是强制模型,而且会带来一些些的问题。例如:java.sql.Driver类,JDK只能提供一个规范接口,而不能提供实现。提供实现的是实际的数据库提供商,提供商的库不可能放JDK目录里。

重写loadclass方法

自定义类加载,重写loadclass方法,即可破坏双亲委派机制

因为双亲委派的机制都是通过这个方法实现的,这个方法可以指定类通过什么类加载器来进行加载,所有如果改写加载规则,相当于打破双亲委派机制

import cn.ybzy.demo.Test;

import java.io.*;

public class MyClassLoader extends ClassLoader {

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData;
        try {
            classData = loadClassData(name);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] loadClassData(String className) throws IOException {
        String replace = className.replace('.', File.separatorChar);
        String path = ClassLoader.getSystemResource("").getPath() + replace + ".class";
        InputStream inputStream = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        try {
            inputStream = new FileInputStream(path);
            byteArrayOutputStream = new ByteArrayOutputStream();
            int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            int length = 0;
            while ((length = inputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, length);
            }
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (byteArrayOutputStream != null) {
                byteArrayOutputStream.close();
            }
            if (inputStream != null) {
                inputStream.close();
            }
        }

        return null;
    }

    @Override
    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 {
                    // 修改classloader的原双亲委派逻辑,从而打破双亲委派
                    if (name.startsWith("cn.ybzy.demo")) {
                        c = findClass(name);
                    } else {
                        c = this.getParent().loadClass(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;
        }
    }
}
    public static void main(String[] args) throws ClassNotFoundException {
        MyClassLoader classLoader = new MyClassLoader();
        Class<?> aClass = classLoader.loadClass(Test.class.getName());
        System.out.println(aClass.getClassLoader());
    }
cn.ybzy.demo.MyClassLoader@2f410acf

自定义类加载器

自定义类加载器的核心在于对字节码文件的获取,如果是加密的字节码则需要在类中对文件进行解密。

准备字节码文件

创建Test类,同时进行javac Test.class编译成字节码文件,放到目录下:D:\Temp\cn\ybzy\demo

package cn.ybzy.demo;

public class Test {
    public static void main(String[] args) {
        System.out.println("Test...");
    }
}

创建自定义类加载器

import java.io.*;

public class MyClassLoader extends ClassLoader {
    private String root;

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData;
        try {
            classData = loadClassData(name);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] loadClassData(String className) throws IOException {
        String fileName = root + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
        InputStream inputStream = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        try {
            inputStream = new FileInputStream(fileName);
            byteArrayOutputStream = new ByteArrayOutputStream();
            int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            int length = 0;
            while ((length = inputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, length);
            }
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (byteArrayOutputStream != null) {
                byteArrayOutputStream.close();
            }
            if (inputStream != null) {
                inputStream.close();
            }
        }

        return null;
    }

    public String getRoot() {
        return root;
    }

    public void setRoot(String root) {
        this.root = root;
    }
}

执行测试

启动main方法,执行测试

    public static void main(String[] args) {
        MyClassLoader classLoader = new MyClassLoader();
        classLoader.setRoot("D:\\Temp");
        Class<?> testClass = null;
        try {
            testClass = classLoader.loadClass("cn.ybzy.demo.Test");
            Object object = testClass.newInstance();
            System.out.println(object.getClass().getClassLoader());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
cn.ybzy.demo.MyClassLoader@5679c6c6

将Test类放到项目类路径下,由于双亲委托机制的存在,会直接导致该类由 AppClassLoader 加载,而不会通过自定义类加载器来加载

sun.misc.Launcher$AppClassLoader@18b4aac2

注意事项

1、这里传递文件名需要是类的全限定性名称,因为defineClass方法是按这种方式/格式进行处理

因此,若没有全限定名,需要将类的全路径加载进去

2、不要重写loadClass方法,因为这样容易破坏双亲委托模式

3、Test类本身可以被AppClassLoader类加载,因此不能把Test.class放在类路径下

否则,由于双亲委托机制的存在,会直接导致该类由AppClassLoader加载,而不会通过自定义类加载器来加载

以上就是JVM类加载器之ClassLoader的使用详解的详细内容,更多关于JVM类加载器ClassLoader的资料请关注编程网其它相关文章!

免责声明:

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

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

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

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

下载Word文档

猜你喜欢

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

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

Java类加载器ClassLoader的使用详解

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

java类加载器ClassLoader详解

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

JVM分析之类加载机制详解

JVM内部架构包含类加载器、内存区域、执行引擎等。日常开发中,我们编写的java文件被编译成class文件后,jvm会进行加载并运行使用类。本次将对JVM加载部分进行分析,便于大家了解并掌握加载机制
2022-11-13

java之jvm加载器使用方法

这篇文章主要讲解了“java之jvm加载器使用方法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“java之jvm加载器使用方法”吧!在java的学习中,对于jvm模块我们会不断补充一些知识点
2023-06-06

如何使用最新版JDK15的JVM类加载器

这篇文章主要讲解了“如何使用最新版JDK15的JVM类加载器”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何使用最新版JDK15的JVM类加载器”吧!1 类加载器在类加载器家族中存在着类似
2023-06-15

Java自定义类加载器及JVM自带的类加载器之间的交互关系是什么

这篇“Java自定义类加载器及JVM自带的类加载器之间的交互关系是什么”文章,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要参考一下,对于“Java自定义类加载器及JVM自带的类加载器之间的交互关系是什么”,小编整理
2023-06-06

Java中线程上下文类加载器超详细讲解使用

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

详解Android之图片加载框架Fresco基本使用(二)

PS:最近看到很多人都开始写年终总结了,时间过得飞快,又到年底了,又老了一岁。学习内容: 1.进度条 2.缩放 3.ControllerBuilder,ControllerListener,PostProcesser,Image Reque
2022-06-06

详解Android之图片加载框架Fresco基本使用(一)

PS:Fresco这个框架出的有一阵子了,也是现在非常火的一款图片加载框架.听说内部实现的挺牛逼的,虽然自己还没研究原理.不过先学了一下基本的功能,感受了一下这个框架的强大之处.本篇只说一下在xml中设置属性的相关用法. 0.引入Fresc
2022-06-06

Android开发中类加载器DexClassLoader的简单使用讲解

简介 “类装载器”(ClassLoader),顾名思义,就是用来动态装载class文件的。标准的Java SDK中有个ClassLoader类,借助此类可以装载需要的class文件,前提是ClassLoader类初始化必须制定class文件
2022-06-06

Qt学习之容器类的使用教程详解

Qt提供了多个基于模板的容器类,这些类可以用于存储指定类型的数据项。本文主要介绍了Qt常用容器类的使用,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2022-12-08

编程热搜

  • 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动态编译

目录