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

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 {//应用类加载器,将拓展类加载器作为应用类加载器的parentthis.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 instanceparallelLockMap = 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 loadedClass<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {//有父类加载器就使用父类加载器加载,例如AppClassLoader和我们不正确地编写自定义类加载器(误将AppClassLoader作为父类加载器)if (parent != null) {c = parent.loadClass(name, false);} else {//针对ExtClassLoaderc = 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 statssun.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 instanceparallelLockMap = 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 {
@CallerSensitivepublic 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.hqwc.cn/news/12902.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mac VSCode配置运行单个C++文件

题外话&#xff1a;VSCode一键整理代码快捷键&#xff1a;ShiftoptionF 方法一&#xff1a;命令行直接编译 g -o 想创建的可执行文件名 ./cpp文件名 ./可执行文件名 以test.cpp为例&#xff0c;我创建的可执行文件名为test&#xff0c;运行结果如下&#xff1a; 方法二&#…

【裸机开发】GPT 定时器(一) —— GPT的功能、寄存器解析

后续需要使用 GPT 计数器实现中断以及延时&#xff0c;这里我们需要先了解一下GPT的功能以及相关寄存器。 目录 一、GPT 定时器的功能 1、计数器 2、输入捕获 3、输出比较&#xff08;GPT的两种工作模式&#xff09; 二、寄存器解析 1、GPTx_CR 2、GPTx_PR 3、GPTx_SR …

3.1.cuda运行API-概述

目录 前言1. Runtime API概述总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习精简 CUDA 教程-Runtime API 概述 课程大纲可…

【编程的多线程学习-前章】什么是进程,PCB进程控制块抽象,cup分配,内存分配,虚拟地址,进程的通信,进程的意义

什么是进程 什么是进程/任务&#xff08;Process/Task&#xff09;进程就是一个运行起来的程序PCB 进程控制块抽象(PCB Process Control Block)pcb就是一个进程PCB具体包含的信息 CPU 分配 —— 进程调度&#xff08;Process Scheduling&#xff09;内存分配 —— 内存管理&…

简单的手机记事本app怎么查看提醒列表?

很多人平时都有随手记事的习惯&#xff0c;在记录事情的时候使用手机上的记事本app是一个不错的选择。有的记事本功能比较完善&#xff0c;不但能记事还能设置提醒&#xff0c;当有多条提醒内容存在时&#xff0c;简单的手机记事本app怎么查看提醒列表呢&#xff1f;以iPhone手…

数据结构--线索二叉树找前驱后继

数据结构–线索二叉树找前驱后继 中序线索二叉树找中序后继 在中序线索二叉树中找到指定结点*p的 中序后继 \color{red}中序后继 中序后继next ①若p->rtag 1&#xff0c;则next p->rchild ②若p->rtag 0 中序遍历――左根右 左根(左根右) 左根((左根右)根右) next …

基于matlab使用多类掩码区域的卷积神经网络对人和汽车的各个实例进行分段(附源码)

一、前言 此示例展示了如何使用基于多类掩码区域的卷积神经网络 &#xff08;R-CNN&#xff09; 对人和汽车的各个实例进行分段。实例分割是一种计算机视觉技术&#xff0c;您可以在其中检测和定位对象&#xff0c;同时为每个检测到的实例生成分割图。 此示例首先演示如何使用…

分治法求最近点对问题

目录 蛮力法 分治法 探究分治规模小于一定程度时采用暴力解法 蛮力法 算法思想 蛮力法&#xff0c;顾名思义&#xff0c;即穷举所有点与点之间的距离&#xff0c;两层循环暴力找出最近点对。算法执行可视化如图1所示&#xff0c;word文档GIF静态显示&#xff0c;附件已含动…

阿里版ChatGPT——通义千问,开箱初体验

所有行业、所有应用、所有服务都值得基于新型人工智能技术重做一遍&#xff0c;在带来创造性客户体验的同时&#xff0c;生产范式、工作范式、生活范式也将发生变化。——阿里集团董事会主席兼CEO 张勇 2023阿里云峰会上&#xff0c;通义千问大语言模型对外发布&#xff0c;宣称…

JAVA环境变量配置步骤及测试(JDK的下载 安装 环境配置教程)

目录 一&#xff1a;JDK的下载、安装和配置 JDK 压缩包版的安装 JDK 安装版的安装 二&#xff1a;环境变量配置步骤 三&#xff1a;测试 四、Eclipse安装 已对此文进行更新&#xff0c;请到新文发布地址&#xff1a;https://rej177.blog.csdn.net/article/details/131565…

SpringBoot通过获取请求参数或者Headers上的特殊标识实现i18n国际化

实现效果 我们大部分都是把i18n的标识放在Headers上面&#xff1b;而把标识放在参数上的话比较少&#xff0c;放参数上的话一般是在使用a标签下载某些文件不好配置请求头的时候才使用上 配置在Headers上面&#xff1a; 配置在params上面&#xff1a; 配置代码&#xff1a; /**…

ChatGPT Prompting开发实战(一)

第7章 ChatGPT Prompting开发实战 7.1 Prompting在LangChain框架中的应用 本节跟大家讲提示工程(Prompt Engineering),主要基于工业级的源码以及具体的项目,无论是工程人员,还是不具有技术背景的人员,大家多少都听说过提示词,或者频繁使用过,简单而言,当我们使用OpenA…