JVM学习-详解类加载器(一)

类加载器

  • 类加载器是JVM执行类加载机制的前提
ClassLoader的作用
  • ClassLoader是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过各种方式将Class信息的二进制数据流读入JVM内部,转换为一个与目标类型对应的java.lang.Class对象实例,然后交给Java虚拟机进行链接,初始化等操作,因此ClassLoader整个装载阶段,只能影响类的加载,而无法通过ClassLoader去改变类的链接和初始化行为,至于是否可以运行,由Execution Engine决定
    在这里插入图片描述
  • 类加载器最早在java1.0版本中,那时只是单纯地为了满足Java Applet应用而被研发出来,但如今类加载器却在OSGi,字节码加解密领域大放异彩,这主要归功于Java虚拟机的设计者们当初在设计类加载器的时候,并没有考虑将它绑定在JVM内部,这样做的好处就是能够更加灵活和动态执行类加载操作。
类加载分类
显式加载
  • 显示加载指的是在代码中通过Classloader加载class对象,如直接使用Class.forName(name)或this.getClass().getClassLoader().loadClass()加载class对象
隐式加载
  • 隐式加载则不直接在代码中调用ClassLoader的方法加载class对象,而是通过虚拟机自动加载到内存中,如在加载某个类的class文件时,该类的class文件中引用了另外一个类的对象,此时额外引用的类将通过JVM自动加载到内存中
类加载必要性
  • 避免在开发中遇到java.lang.ClassNotFoundException异常或java.lang.NoClassDefFoundError异常时,手足无措,只有了解类加载器的加载机制才能在出现异常时根据错误异常日志定位问题和解决问题
  • 需要支持类的动态加载或需要对编译后的字节码文件进行加密操作时,就需要与类加载器打交道了
  • 开发人员可以在程序中编写自定义类加载器来重新定义类的加载规则,以便实现一些自定义的处理逻辑
命名空间
何为类的唯一性
  • 对于任意一个类,都需要加载它的类加载器和这个类本身一同确认其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间,**比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义。**否则,即使这两个类源自同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等
命名空间
  • 每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父加载器所加载的类组成
  • 在同一个命名空间中,不会出现类的完整名字相同的两个类
  • 在不同的命名空间中,有可能会出现类的完整名字相同的两个类
类加载机制的基本特征
  • 双亲委派模型:不是所有类加载都遵守这个模型,启动类加载器所加载的类型,是可能要加载用户代码的,比如JDK内部的ServiceProvider/ServiceLoader机制,用户可以在标准API框架上,提供自己的实现,JDK也需要提供些默认的参考实现,如Java中JNDI,JDBC,文件系统,Cipher等很多方面,都是利用这种机制,这种情况不会用双亲委派模型去加载,而是利用所谓的上下文加载器
  • 可见性:子类加载器可以访问父类加载器加载的类型,但是反过来是不允许的,不然,因为缺少必要的隔离,我们就没有办法利用类加载器去实现容器的逻辑
  • 单一性:由于父加载器的类型对于子加载器可见,所以父加载器中加载过的类型,就不会在子加载器中重复加载,但是注意,类加载器“邻居”间,同一类型仍然可以被加载多次,因为互相并不可见。
类加载器
  • JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)
  • 从概念上讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么定义,将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器,无论类加载器的类型如何划分,在程序中我们最常见的类加载器结构如下:
    在这里插入图片描述
  • 除了顶层的启动类加载器外,其余的类加载器都应有自己的“父类”加载器
  • 不同类加载器看似是继承(Inheritance)关系,实际上是包含关系,在下层加载器中,包含着上层加载器的引用
引导类加载器
  • 这个类加载器使用C/C++语言实现,嵌套在JVM内部
  • 它用来加载Java核心类库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容)。用于提供JVM自身需要的类
  • 并不继承自java.lang.ClassLoader,没有父加载器
  • 出于安全考虑,Bootstrap启动类加载器只加载包名为java,javax,sun等开头的类
  • 加载扩展类加载器和应用程序类加载器,并指定为他们的父类加载器
  • 使用-XX:+TraceClassLoading
    在这里插入图片描述
扩展类加载器
  • Java语言编写,由sun.misc.Launcher$ExtClassLoader实现
  • 继承于ClassLoader类
  • 父类加载器为启动类加载器
  • 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库,如用户创建的JAR放在此目录下,也会自动由扩展类加载器加载
    在这里插入图片描述
    类加载器实验
系统类加载器(应用程序类加载器)
  • java语言编写,由sun.misc.Launcher$AppClassLoader实现
  • 派生于ClassLoader类
  • 父类加载器为扩展类加载器ExtClassLoader
  • 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
  • 该类为程序默认的类加载器,java应用的类由它来完成加载
  • 通过ClassLoader#getSystemClassLoader()方法可以获得此类加载器
用户自定义类加载器
  • 在Java的日常应用开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式
  • 体现Java语言的强大生命力和巨大魅力的关键因素之一便是,Java开发者可以自定义类加载器来实现类库的动态加载,加载源可以是本地的JAR包,也可以是网络上的远程资源
  • 通过类加载器可以实现非常绝妙的插件机制,这方面的实际应用安全举不胜举,例如,著名的OSGI组件框架,再如Eclipse的插件机制,类加载器为应用程序提供一种动态增加新功能的机制,这种机制无须重新打包发布应用程序就能实现
  • 同时,自定义加载器能够实现应用隔离,例如Tomcat,Spring等中间件和组件框架都在内部实现自定义的加载器,并通过自定义加载器隔离不同的组件模块,这种机制比C/C++程序要好很多,想不修改C/C++程序就能为其新增功能,几乎是不可能的,仅一个兼容性便能阻挡住所有美好设想
  • 自定义类加载器通常需要继承于ClassLoader
ClassLoader源码解析
ClassLoader与现有类加载器的关系

在这里插入图片描述

  • 除了以上虚拟机自带的加载器外,用户还可以定制自己的类加载器,Java提供了抽象类java.lang.ClassLoader,所有用户自定义的类加载器继承ClassLoader
抽象类ClassLoader主要方法
  • public final ClassLoader getParent():返回类加载器的超类加载器
  • public Class<?> loadClass(String name) throws ClassNotFoundException :加载名称为name的类,返回结果为java.lang.Class类的实例,如果找不到类,则返回ClassNotFoundException异常,该方法中的逻辑就是双亲委派模式的实现
//测试代码ClassLoader.getSystemClassLoader().loadClass("com.chapter11.User")涉及以下方法调用
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {  //同步代码,保证多个线程只能有一个运行
            // 首先,在缓存中判断是否已经加载同名的类
            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) {   //当前类加载器的父类加载器未加载此类或当前类加载器未加载此类
                    // 调用当前ClassLoader的findClass()
                    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;
        }
    }
  • protected Class<?> findClass(String name) throws ClassNotFoundException:查找二进制名称为name的类,返回结果为java.lang.Class类的实例,这是一个受保护的方法,JVM鼓励我们重写此方法,需要自定义加载器遵循双亲委派机制,该方法会在检查完父类加载器之后被loadClass()方法调用
  • 在JDK1.2之前,在自定义类加载时,总会去继承ClassLoader类并重写loadClass方法,从而实现自定义的类加载类,1.2后不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加载逻辑写在findClass()方法中,从前面分析可知,findClass()方法是在loadClass()方法中被调用,当loadClass()方法中父加载器加载失败后,则会调用自己findClass()方法来完成类加载,这样就可以保证自定义类加载器也符合双亲委派模式
  • ClassLoader类中并没有实现findClass()方法的具体代码逻辑,取而代之的是抛出ClassNotFoundException异常,同时应该知道的是findClass方法通常和defineClass方法一起使用,一般情况下,在自定义加载器时,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()方法生成类的Class对象
//在URLClassLoader中重写了findClass方法,代码如下
protected Class<?> findClass(final String name)
        throws ClassNotFoundException
    {
        final Class<?> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                        String path = name.replace('.', '/').concat(".class");
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            return null;
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
        if (result == null) {
            throw new ClassNotFoundException(name);
        }
        return result;
    }
  • protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError:根据给定的字节数组b转换为Class实例,off和len参数表示实际Class信息在byte数组中的位置和长度,其中byte数组b是ClassLoader从外部获取的,这是受保护的方法,只有在自定义ClassLoader子类中可以使用
  • defineClass()方法是用来将byte字节流解析成JVM能够识别的Class对象,通过这个方法不仅能够通过class文件实例化class对象,也可以通过其他方式实例化对象,如通过网络接收一个类的字节码,然后转换为byte字节流创建对应的Class对象
  • defineClass()方法通常与findClass()方法一起使用,一般情况下,在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载类的字节码转换成流,然后调用defineClass()方法生成类的Class对象
private Class<?> defineClass(String name, Resource res) throws IOException {
        long t0 = System.nanoTime();
        int i = name.lastIndexOf('.');
        URL url = res.getCodeSourceURL();
        if (i != -1) {
            String pkgname = name.substring(0, i);
            // Check if package already loaded.
            Manifest man = res.getManifest();
            definePackageInternal(pkgname, man, url);
        }
        // Now read the class bytes and define the class
        java.nio.ByteBuffer bb = res.getByteBuffer();
        if (bb != null) {
            // Use (direct) ByteBuffer:
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            return defineClass(name, bb, cs);
        } else {
            byte[] b = res.getBytes();
            // must read certificates AFTER reading bytes.
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            return defineClass(name, b, 0, b.length, cs);
        }
    }
  • protected final void resolveClass(Class<?> c) :链接指定的一个Java类,使用该方法可以使用类的Class对象创建完成的同时也被解析,前面我们说链接阶段主要是对字节码进行验证,为类变量分配内存并设置初始值同时将字节码文件中的符号引用转换为直接引用
  • protected final Class<?> findLoadedClass(String name):查找名称为name的已经被加载过的类,返回结果为java.lang.Class类的实例,这个方法是final方法,无法被修改
  • private final ClassLoader parent :它也是一个ClassLoader的实例,这个字段表示的ClassLoader也称为这个ClassLoader的双亲,在类加载过程中,ClassLoader可能会将某些请求交予自己的双亲处理
SecureClassLoader与URLClassLoader
  • SecureClassLoader扩展了ClassLoader,新增了几个与使用相关的代码源和权限定义类验证的方法,一般不直接与此类打交道,更多与其子类URLClassLoader有所关联
  • ClassLoader是一个抽象类,很多方法是空没有实现,如findClass,findResource等,而URLClassLoader这个实现类为这些方法提供了具体实现,新增了URLClassPath类协助取得Class字节码流等功能,在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自己去编写findClassLoader()方法及其获取字节码流的方式,使自定义类更加简洁
ExtClassLoader与AppClassLoader
  • 这两个类继承URLClassLoader,是sun.misc.Launcher的静态内部类,sun.misc.Launcher主要被系统用于启动主应用程序,ExtClassLoader和AppClassLoader都由sun.misc.Launcher创建
  • ExtClassLoader没有重写loadClass()方法,AppClassLoader重写了loadClass方法,但最终调用还是父类的loadClass()方法,因此两个类都遵循双亲委派模式
Class.forName()与ClassLoader.loadClass()
  • Class.forName()是一个静态方法,最常用的Class.forName(String className)根据传入的类的全限定名返回一个Class对象,该方法在将Class文件加载到内存的同时,会执行类的初始化
  • ClassLoader.loadClass():是一个实例方法,需要一个ClassLoader对象来调用该方法,该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化,该方法因为需要得到一个ClassLoader对象,所以可以根据需要指定使用哪个类加载器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/669473.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

什么是TLAB?

这个得从内存申请说起。 一般而言生成对象需要向堆中的新生代申请内存空间&#xff0c;而堆又是全局共享的&#xff0c;像新生代内存又是规整的&#xff0c;是通过一个指针来划分的。 内存是紧凑的&#xff0c;新对象创建指针就右移对象大小size即可&#xff0c;这叫指针加法…

Linux 的权限

目录 Linux 的用户 root 用户 和 普通用户 如何新建普通用户&#xff1f; 如何切换用户&#xff1f; 一开始是以 root 用户登录&#xff1a; 一开始以普通用户登录&#xff1a; 如何删除用户&#xff1f; Linux文件权限 什么是读权限&#xff08; r &#xff09;&#…

数据结构学习笔记

1. 数组 (Array) 定义 数组是一种线性数据结构&#xff0c;用于存储固定大小的相同类型元素集合。每个元素都有一个索引&#xff0c;用于快速访问。 特点 优点&#xff1a;访问速度快&#xff0c;通过索引直接访问O(1)时间复杂度。缺点&#xff1a;大小固定&#xff0c;插入…

Unity 自定义房间布局系统 设计与实现一个灵活的房间放置系统 ——自定义房间区域功能

自定义房间区域功能 效果&#xff1a; 功能&#xff1a; 能够自定义房间的大小一键生成放置区域可控的放置网格点当物体放置到区域内可自动吸附物体是否可放置&#xff0c;放置时如果与其他物体交叉则不可放置&#xff08;纯算法计算&#xff09;管理房间内的物体&#xff0c…

MySQL(二)-基础操作

一、约束 有时候&#xff0c;数据库中数据是有约束的&#xff0c;比如 性别列&#xff0c;你不能填一些奇奇怪怪的数据~ 如果靠人为的来对数据进行检索约束的话&#xff0c;肯定是不行的&#xff0c;人肯定会犯错~因此就需要让计算机对插入的数据进行约束要求&#xff01; 约…

下载HF AutoTrain 模型的配置文件

下载HF AutoTrain 模型的配置文件 一.在huggingface上创建AutoTrain项目二.通过HF用户名和autotrain项目名,拼接以下url,下载模型列表(json格式)到指定目录三.解析上面的json文件、去重、批量下载模型配置文件(权重以外的文件) 一.在huggingface上创建AutoTrain项目 二.通过HF用…

电脑没电关机,wsl和docker又挂了,附解决过程

如题&#xff0c;开了个会没带笔记本电源&#xff0c;点啊弄关机后docker打不开&#xff0c;我以为是docker坏了&#xff0c;结果docker报错&#xff1a; An unexpected error occurred while executing a WSL command. Either shut down WSL down with wsl --shutdown, and/or…

植被变化趋势线性回归以及可视化

目录 植被变化线性回归ee.Reducer.linearFit().reduce()案例:天水市2004-2023年EVI线性回归趋势在该图中,使用了从红色到蓝色的渐变来表示负趋势到正趋势。红色代表在某段时间中,植被覆盖减少,绿色表示持平,蓝色表示植被覆盖增加。 植被变化线性回归 该部分参考Google…

Java(十一)---String类型

文章目录 前言1.String类的重要性2.常用方法2.1.字符串的创建2.2.字符串的比较2.2.1.比较是否引用同一个对象2.2.2.boolean equals(Object anObject) 方法&#xff1a;2.2.3.int CompareTo(String s)2.2.4.int compareToIgnoreCase(String str) 方法&#xff1a; 2.3.字符串的查…

单调栈原理+练习

首先用一道题引出单调栈 码蹄集 (matiji.net) 首先画一个图演示山的情况&#xff1a; 最暴力的做法自然是O(n方)的双循环遍历&#xff0c;这么做的思想是求出当前山右侧有多少座比它小的山&#xff0c;遇见第一个高度大于等于它的就停止。 但是对于我们所求的答案数&#xff…

能不能接受这些坑?买电车前一定要看

图片来源&#xff1a;汽车之家 文 | Auto芯球 作者 | 雷慢 刚有个朋友告诉我&#xff0c;买了电车后感觉被骗了&#xff0c; 很多“坑”都是他买车后才知道的。 不提前研究&#xff0c;不做功课&#xff0c;放着我这个老司机不请教&#xff0c; 这个大冤种他不当谁当&…

C# WinForm —— 25 ProgressBar 介绍与使用

1. 简介 用于显示某个操作的进度 2. 常用属性 属性解释(Name)控件ID&#xff0c;在代码里引用的时候会用到,一般以 pbar 开头ContextMenuStrip右键菜单Enabled控件是否可用ForeColor用于显示进度的颜色MarqueeAnimationSpeed进度条动画更新的速度&#xff0c;以毫秒为单位M…

【Python】解决Python错误报错:IndexError: tuple index out of range

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

什么是PLAB?

接上文PLAB---》 可以看到和TLAB很像&#xff0c;PLAB即 Promotion Local Allocation Buffers。用在年轻代对象晋升到老年代时。 在多线程并行执行YGC时&#xff0c;可能有很多对象需要晋升到老年代&#xff0c;此时老年代的指针就"热"起来了&#xff0c;于是搞了个…

新游启航 失落的方舟台服注册指南 一文教会你方舟台服注册

新游启航&#xff01;失落的方舟台服注册指南&#xff01;一文教会你方舟台服注册 失落的方舟作为本月最受期待游戏之一&#xff0c;在上线之际许多玩家已经有点急不可待了。这款游戏是由开发商Smile gate开发的一款MMORPG类型游戏&#xff0c;这款游戏的基本玩法与其他MMORPG…

44-1 waf绕过 - WAF的分类

一、云 WAF 通常包含在 CDN 中的 WAF。在配置云 WAF 时&#xff0c;DNS 需要解析到 CDN 的 IP 上。请求 URL 时&#xff0c;数据包会先经过云 WAF 进行检测&#xff0c;如果通过检测&#xff0c;再将数据包流向主机。 二、硬件IPS/IDS防护、硬件WAF 硬件IPS/IDS防护&#xff…

归并排序C++代码详解,思路流程+代码注释,带你完全学会归并排序

归并排序 归并排序是一种经典的排序算法&#xff0c;属于分治算法的一种。其核心思想是将一个大问题分解成若干个较小的子问题来解决&#xff0c;然后根据子问题的解构建原问题的解。在排序问题中&#xff0c;归并排序将数组分成两半&#xff0c;分别对这两半进行排序&#xf…

0基础认识C语言(理论+实操3)

所有籍籍无名的日子里 我从未看轻自己半分 小伙伴们&#xff0c;一起开始我们今天的话题吧 一、算法操作符 1.双目操作符 为何叫双目操作符呢&#xff1f;其实是因为我们进行加减乘除的时候&#xff0c;至少得需要两个数字进行这些运算&#xff0c;而这个数字就被称为操作数…

Java对sqlserver表的image字段图片读取和输出本地

Java代码实现对sqlserver数据库表的image字段图片的读取&#xff0c;和输出存储到本地 由于表image字段图片存的内容是二进制值&#xff0c;如何输出保存到本地&#xff1a; 代码示例&#xff1a;&#xff08;注&#xff1a;连接sqlserver数据库需配置其驱动文件&#xff09; …

基础—SQL—DCL(数据控制语言)之用户管理

一、引言 分类全称描述DCLData Control Language&#xff08;数据控制语言&#xff09;用来创建和管理数据库用户以及控制数据库的访问权限 1、图解 右边的是我们的 MySQL 的数据库服务器&#xff0c;左边是假设的两个用户 1、 DCL 主要控制的就是有哪些用户可以来访问这台 My…