Java类加载深度剖析-大白话

news2024/12/22 19:15:07

Java类加载深度剖析

    • 1.类加载的入口
    • 2.AppClassLoader、ExtClassLoader、BootstrapClassLoader的血脉渊源
    • 3.ExtClassLoader究竟是不是孙大圣
    • 4.为什么自定义类加载器的父类加载器是AppClassLoader呢?
    • 5.我们应该如何打破双亲委派机制呢?
    • 6.如何保证同class对象是唯一的
    • 7.确定是否是同一个类对象取决于类加载器的类型还是说在类型相同的基础上还得是同一个具体对象(内存地址要相同)
    • 8.心得推论

1.类加载的入口

sum.misc.Launcher:
它是一个java虚拟机的入口应用。
这里我们可以发现BootstrapClassLoader的加载路径:System.getProperty("sun.boot.class.path")

private static URLStreamHandlerFactory factory = new Launcher.Factory();
    private static Launcher launcher = new Launcher();
    private static String bootClassPath = System.getProperty("sun.boot.class.path");
    private ClassLoader loader;
    private static URLStreamHandler fileHandler;

    public static Launcher getLauncher() {
        return launcher;
    }

    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
        //拓展类加载器
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
        //应用类加载器,将拓展类加载器作为应用类加载器的parent
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
		//设置当前线程上下文加载器是应用类加载器
        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                } catch (InstantiationException var6) {
                } catch (ClassNotFoundException var7) {
                } catch (ClassCastException var8) {
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

    }

2.AppClassLoader、ExtClassLoader、BootstrapClassLoader的血脉渊源

上面代码片段java虚拟机的入口应用实例化了ExtClassLoader和AppClassLoader,那么我们看看实例化这两个ClassLoader的时候有没有对它们有额外的设置,比如双亲委派中的parent:
AppClassLoader:
这里也可以看出AppClassLoader的加载路径System.getProperty("java.class.path");

public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
            final String var1 = System.getProperty("java.class.path");
            final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
            return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
                public Launcher.AppClassLoader run() {
                    URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                    return new Launcher.AppClassLoader(var1x, var0);
                }
            });
        }

new Launcher.AppClassLoader(var1x, var0);var0是ExtClassLoader(1中提及过),一路追踪上去,追踪到了顶层父类ClassLoader的方法中:

// The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    private final ClassLoader parent;
    private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            assertionLock = this;
        }
    }

之前一直说ExtClassLoader 的父类加载器是null,那么我们也遵循同样的思路一探究竟:

static class ExtClassLoader extends URLClassLoader {
        private static volatile Launcher.ExtClassLoader instance;

        public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
            if (instance == null) {
                Class var0 = Launcher.ExtClassLoader.class;
                synchronized(Launcher.ExtClassLoader.class) {
                    if (instance == null) {
                        instance = createExtClassLoader();
                    }
                }
            }

            return instance;
        }

注意,跟AppClassLoader一样的逻辑,但这里的ClassLoader是null,这也就解释了日常输出的ExtClassLoader 的父类加载器是null。

public ExtClassLoader(File[] var1) throws IOException {
            super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
            SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
        }

3.ExtClassLoader究竟是不是孙大圣

既然ExtClassLoader父类加载器为null,那么双亲委派机制中的ExtClassLoader委托BootstrapClassLoader加载类又是怎么一回事呢?jdk从加载类的逻辑去实现这个功能:
在ClassLoader类中:

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 {
                //有父类加载器就使用父类加载器加载,例如AppClassLoader和我们不正确地编写自定义类加载器(误将AppClassLoader作为父类加载器)
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                    //针对ExtClassLoader
                        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;
        }
    }

4.为什么自定义类加载器的父类加载器是AppClassLoader呢?

那就从构造方法的执行顺序中说起了,我们自定义的ClassLoader会继承ClassLoader,执行自定义的ClassLoader之前会执行ClassLoader的构造方法,一般是无参构造方法。

    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }
    private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            assertionLock = this;
        }
    }

对比可得我们重点关注getSystemClassLoader()方法
追踪:
initSystemClassLoader()->

private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
                //懒汉模式,全局单例
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
                Throwable oops = null;
                //重点
                scl = l.getClassLoader();
                try {
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                } catch (PrivilegedActionException pae) {
                    oops = pae.getCause();
                    if (oops instanceof InvocationTargetException) {
                        oops = oops.getCause();
                    }
                }

在这里插入图片描述
我们之前提及过this.loader被设置为AppClassLoader,因此自定义类加载器的父类加载器是AppClassLoader。

5.我们应该如何打破双亲委派机制呢?

与其说打破双亲委派机制,不如说如何将我们要加载的class与我们自定义的类加载器建立联系,而不是委派AppClassLoader加载。
我们平常自定义类加载器的时候可能在不经意的时候在重写loadClass方法的时候调用了getParent().loadClass(name);这就导致了使用AppClassLoader去加载类。

public abstract class ClassLoader {
@CallerSensitive
    public final ClassLoader getParent() {
        if (parent == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // Check access to the parent class loader
            // If the caller's class loader is same as this class loader,
            // permission check is performed.
            checkClassLoaderPermission(parent, Reflection.getCallerClass());
        }
        return parent;
    }
    }

那么自定义类加载器和类是如何建立联系的呢?这里先放个彩蛋:
注意this(每new一个classLoader都会调这个方法将自身设置进去)

 // The "default" domain. Set as the default ProtectionDomain on newly
    // created classes.
    private final ProtectionDomain defaultDomain =
        new ProtectionDomain(new CodeSource(null, (Certificate[]) null),
                             null, this, null);

重点关注protectionDomain = preDefineClass(name, protectionDomain);Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);前者封装classLoader,后者将classLoader和类对象建立关系

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加载类的时候,最终都会调用这个方法Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
每个classLoader都对应一个defaultDomain
在这里插入图片描述
最后调用本地方法生成class对象并且与classLoader建立联系。

    private native Class<?> defineClass1(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd, String source);

综上所述:要打破双亲委派机制,关键点是想办法让真正的ProtectionDomain 与自定义的类加载器关联,要做到这一点,就不能在重写loadClass方法中调用getParent().loadClass(name);方法。而且,往往会存在一种幻觉,new 自定义类加载器的时候ProtectionDomain 是封装了自定义类加载器,但是deBug到protectionDomain = preDefineClass(name, protectionzDomain);的时候发现是AppClassLoader或者其他上层加载器,那就是因为在重写loadClass方法中调用getParent().loadClass(name);方法

6.如何保证同class对象是唯一的

参考地址
jvm使用包路径的类名和类加载器唯一确定了一个类
怎么说呢,在每次调用loadClass方法将要类的时候,内部会首先调用Class<?> c = findLoadedClass(name);JDK官方源码注释:First, check if the class has already been loaded,中译:首先,检查这个类是否已经加载过了,如果返回的引用不为空,就将这个class对象返回去,同时,这个方法最终会调用本地方法,这个本地方法的作用就如上所述:jvm使用包路径的类名和类加载器唯一确定了一个类。

7.确定是否是同一个类对象取决于类加载器的类型还是说在类型相同的基础上还得是同一个具体对象(内存地址要相同)

答案:确定是否是同一个类对象取决在类加载器的类型相同的基础上还得是同一个具体对象(内存地址要相同),验证思路:自定义类加载器,加载过程不得调用父加载器,new两个类加载器和使用同一个类加载器去加载这个类,如果class对象相同说明判定一个类对象是否是同一个门槛并不高,仅仅取决于是不是同样的类加载器类型,反之说明门槛稍高:得是同样的类加载器类型并且是同一个类加载器对象才可以。

8.心得推论

重新认识一个类对象和改类对象类型的实例对象,java万物皆对象,平常所说的类模板衍生出实例对象,其实这个类模板就是类对象,而且在jvm中只有一个,朦胧之中就可以将jvm将class文件加载到内存落地生成类对象(方法区-公共-模板-唯一)作为入口联系起来。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

实现UDP通信

UDP通信的实现过程 write/read到send/recv 函数原型&#xff1a; ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t recv(int sockfd, void *buf, size_t len, int flags); 前三个参数同read/write一样&#xff1b; ssize_t read(int fd, void *bu…

滚珠螺杆的基本知识点

滚珠螺杆具有定位精度高、高寿命、低污染和可做高速正逆向的传动及变换传动等特性&#xff0c;因具上述特性&#xff0c;滚珠螺杆已成为近来精密科技产业及精密机械产业的定位及测量系统上的重要零组件之一。 滚珠螺杆主要由螺杆、螺帽、钢珠、固定座、刮刷器及回流管所构成的&…

thinkphp开发宠物领养商城系统 金融投资理财源码 中英文语言 支持增加多种语言

程序代码里面 除了线下支付&#xff0c;增加了4个线上支付方式 1、新增短信接口&#xff1a;短信宝、云片短信、网建短信。 2、新增阿里API实名认证&#xff0c;支持开启或关闭。 3、新增阿里API银行卡实名认证&#xff0c;支持开启或关闭。 4、新增项目分类开关、支付宝和微信…

基于springboot+vue框架的电影订票系统_wqc3k

随着网络科技的不断发展以及人们经济水平的逐步提高&#xff0c;计算机如今已成为人们生活中不可缺少的一部分&#xff0c;为电影订票方便管理&#xff0c;基于java技术设计与实现了一款简洁、轻便的管理系统。本系统解决了电影订票事务中的主要问题&#xff0c;包括个人中心、…

微服务拆分原则

库存供应链服务 交易和订单服务 用户服务 1. 业务之间耦合降低 相互调用较少 进行拆分 2.修改频率区分不同服务

Qlib全新升级:强化学习能否重塑金融决策模式?

编者按&#xff1a;2020年&#xff0c;微软亚洲研究院开源了金融 AI 通用技术平台 Qlib。Qlib 以金融 AI 研究者和金融行业 IT 从业者为用户&#xff0c;针对金融场景研发了一个适应人工智能算法的高性能基础设施和数据、模型管理平台。一经开源&#xff0c;Qlib 便掀起了一阵热…

对话罗氏中国:数字化创新驱动下的高效运营与合规实践

上海斯歌与罗氏中国已合作十余年&#xff0c;罗氏几乎见证了上海斯歌发展的全过程。近日&#xff0c;斯歌与罗氏 China Market Domain 架构负责人—— Brian Yang&#xff0c;就企业数字化探索、新技术的展望及斯歌产品等话题展开了探讨。 罗氏中国简介 罗氏&#xff08;Roche&…

Nacos启动报错

错误如下 org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name communicationController defined in URL [jar:file:/D:/apache/maven/vip/xiaonuo/nacos/nacos-config/2.1.2.GA/nacos-config-2.1.2.GA.jar!/com/alibaba/nacos…

前后端实现mqtt物联网消息互通对话(图文详解)

需求&#xff1a;前端使用mqtt订阅主题和硬件设备进行通讯功能&#xff0c;不走后端&#xff0c;前端操作可以控制。从部署到对话&#xff0c;跟着图文一套下来你也可以学会。很简单的。后端用node&#xff0c;前端就用原生的js&#xff0c;如果要使用vue&#xff0c;可以看我另…

OpenCV4使用applyColorMap()函数,可以将灰度图或彩色图转换成自定义的彩色图,或opencv提供的20多种色彩值

文章目录 1、applyColorMap()函数的使用&#xff1a;&#xff08;1&#xff09;函数原型&#xff1a;void applyColorMap(InputArray src, OutputArray dst, int colormap)void applyColorMap(InputArray src, OutputArray dst, InputArray userColor) &#xff08;2&#xff0…

记录CompletableFuture使用遇到的坑-多数据源

现象&#xff1a;使用了allof().get()去阻塞线程等待子线程跑完&#xff0c;但子线程还是没跑完就结束了。 代码如图&#xff1a; 如果您熟悉CompletableFuture的.allOf方法应该知道题主是想等3个异步任务完成再往下执行&#xff0c;但事实是3个异步任务执行到最后 "好像…

2023年最新全国分省、市路网数据shp

最新全国分省、市路网数据 2023年 ​ 最近有小伙伴反应无法进入OSM地图官网下载数据&#xff0c;所以这次带来全国路网数据shp文件。获取时间&#xff1a;2023年5月 数据格式&#xff1a;shp 坐标系&#xff1a;GCJ-02 数据概览 分省如下&#xff1a; ​ 例如河北&#xff…

DDR3 控制器 MIG IP 详解完整版 (nativeVIVADOVerilog)

文章目录 前言一、MIG IP 核的配置二、MIG 交互的接口三、常用IP例化值四、小实验传图 前言 本节主要是介绍 Xilinx DDR 控制器 IP 的创建流程、IP 用户使用接口 native 协议介绍和IP 对应的 Example Design 的仿真和上板验证。。 提示&#xff1a;以下是本篇文章正文内容&…

开放式耳机哪个好?综合性能不错的开放式耳机推荐

传统入耳式耳机容易滑落&#xff0c;而且戴久了耳朵疼&#xff0c;开放式耳机的出现就避免了这个问题的出现&#xff0c;本文就为大家推荐几款使用感较好的开放式耳机&#xff0c;一起来看看吧~ 一、NANK南卡OE Pro开放式耳机 南卡OE Pro凭借着顶级的佩戴体验和极高的音质水准…

图像几何变换笔记

图像缩放 图像缩放是指对图像大小进行调整&#xff0c;对原图进行放大或缩小。图像缩放一般通过插值采样来实现。 常见的插值方法&#xff1a;最近邻插值、双线性插值、双三次插值。 最近邻插值 最近邻插值是最简单的一种插值方法&#xff0c;通过映射将原始图片中的像素值映射…

【数据结构与算法】哈希表设计(C\C++)

实践要求 1. 问题描述 针对某个集体中人名设计一个哈希表&#xff0c;使得平均查找长度不超过R&#xff0c;并完成相应的建表和查找程序。 2. 基本要求 假设人名为中国人姓名的汉语拼音形式。待填入哈希表的人名共有30个&#xff0c;取平均查找长度的上限为2。哈希函数用除留…

C++——string(2)

作者&#xff1a;几冬雪来 时间&#xff1a;2023年7月7日 内容&#xff1a;C——string内容讲解 目录 前言&#xff1a; 1.string&#xff1a; 1. reserve&#xff1a; 2.resize&#xff1a; 3.assign&#xff1a; 4.insert&#xff1a; 5.erase&#xff1a; 6.rep…

策略模式深度实践——通用的HTTP接口调用

个人主页&#xff1a;金鳞踏雨 个人简介&#xff1a;大家好&#xff0c;我是金鳞&#xff0c;一个初出茅庐的Java小白 目前状况&#xff1a;22届普通本科毕业生&#xff0c;几经波折了&#xff0c;现在任职于一家国内大型知名日化公司&#xff0c;从事Java开发工作 我的博客&am…

【CSS卡片翻转特效】CSS3实现3D旋转动画卡片翻转效果(附源码)

文章目录 写在前面涉及知识点效果展示1、web页面的搭建1&#xff09;创建dom节点2&#xff09;DOM元素添加图片3&#xff09;添加翻转后的文字 2、CSS效果的实现1&#xff09;div本身翻转效果2&#xff09;3D翻转效果完整CSS3实现翻转效果demo代码可以留言邮箱或者自己去百度网…

扒开 TCP 的外衣,看清 TCP 的本质

TCP 非常重要&#xff0c;它的内容很多&#xff0c;今天只能讲解其中的一部分&#xff0c;但足以让你超越 80 % 的编程开发人员对于 TCP 的认知。 本篇内容非常多&#xff0c;非常干&#xff0c;希望你花点时间仔细研究&#xff0c;我相信会对你有所帮助。 1. TCP 协议是什么…