百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

【JVM类加载】系统自带的类加载器如何加载如何自定义类加载器

nanshan 2024-11-10 10:11 35 浏览 0 评论

系统自带的类加载器如何加载(重要)

  • 内建于JVM中的启动类加载器,会加载java.lang.classLoader以及其他平台的Java平台类,当JVM启动时,一块特殊的机器码会运行,他会加载扩展类加载器与系统类加载器,这块特殊的机器码叫做启动类加载器(Bootstrap)
  • 启动类加载器并不是java类(C++编写),而其他的加载器则都是Java类,启动类加载器是特定于平台的机器指令,它负责开启整个加载过程。
  • 所有类加载器(除了启动类加载器)都被实现为java类。不过,总归要有一个组件来加载第一个Java类加载器,从而让整个加载过程能够顺利进行下去,加载第一个纯java类加载器就是启动类的职责。
  • 启动类加载器还会负责加载供JRE正常运行所需要的基本组件,这包括java.util与java.lang包中的类等等

线程上下文类加载器的一般使用模式(获取->使用->还原)


    线程上下文类加载器的一般使用模式(获取->使用->还原)
    //获取
        ClassLoader classLoader = Thread.currentThread.getContextClassLoader();
    try{
        //设置要使用的类加载器
        //如cls就是启动类加载器就可以加载到实现类了 
        //springboot 加载MATE-INF/下spring.factories 就是采用的线程类加载器
        Thread.currentThread.setContextClassLoader(cls);
        //使用
        MyMethod();
    }finally{
        //如果不还原使用的就是设置的类加载器
        //还原
        Thread.currentThread.setContextClassLoader(classLoader);
    }

    

MyMethod里面则调用了Thread.currentThread().getContextClassLoader(),获取当前线程的上下文类加载器做某些事情。如果一个类由类加载器A加载,那么这个类的依赖类也是由相同的类加载器加载的(如果该依赖类之前没有被加载过的话)如: 启动类加载器扫描不到系统类加载器的内容 上下文类加载器就是为了解决这个问题

ContextClassLoader的作用就是为了破坏Java的类加载委托机制(如上)

当高层提供了统一的接口让低层去实现,同时又要在高层加载(或实例化)底层的类时,就必须要通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类


我们可以直接使用getClassLoader获取系统类加载器去加载对应的类为什么还要使用上下文类加载器?

  1. 方便,执行的任何代码都在线程中 我们可以随时取出来对应的上下文类加载器使用
  2. 解决高层加载底层类问题
  3. 有特定的情况,当前的线程类加载器 不一定是系统类加载器 此时家不能加载classpath下的.class文件

自定义系统类加载器


/*
    在运行期,一个java类是由该类的完全限定名(binary name,二进制名) 和用于加载该类的定义类加载器(defining loader)所共同决定的。
    如果同样名字(即相同的完全限定名)的类是由两个不同的加载器所加载,那么这些类就是不同的,即便.class文件的字节码完全一样,并且从
    相同的位置加载亦如此。
 */

/*
    在Oracle的Hotspot实现中,系统属性sun.boot.class.path如果修改错了,则运行会出差,提示如下错误信息:

    Error occurred during initialization of VM
    java/lang/NOClassDefFoundError: java/lang/Object
 */

public class MyTest23 {
    public static void main(String[] args) {
        System.out.println(System.getProperty("sun.boot.class.path"));//根类加载器
        System.out.println(System.getProperty("java.ext.dirs"));//扩展类加载器
        System.out.println(System.getProperty("java.class.path"));//应用类加载器  当在编译之后的.class文件会被放到classes下 所以会被该类加载器加载

        /*

            内建于JVM中的启动类加载器,会加载java.lang.classLoader以及其他平台的Java平台类,
            当JVM启动时,一块特殊的机器码会运行,他会加载扩展类加载器与系统类加载器,
            这快特殊的机器码焦作启动类加载器(Bootstrap).

            启动类加载器并不是java类,而其他的加载器则都是Java类,
            启动类加载器是特定于平台的机器指令,它负责开启整个加载过程。

            所有类加载器(除了启动类加载器)都被实现为java类。不过,总归要有一个组件来加载第一个Java类加载器,从而让整个加载过程能够顺利进行下去,加载第一个纯java类加载器就是启动类
            的职责。

            启动类加载器还会负责加载供JRE正常运行所需要的基本组件,这包括java.util与java.lang包中的类等等

         */

        System.out.println(ClassLoader.class.getClassLoader());//null 内建于JVM中的启动类加载器,会加载java.lang.classLoader以及其他平台的Java平台类,

        //扩展类加载器与系统类加载器也是由启动类加载器加载的
        System.out.println(Launcher.class.getClassLoader());//null 因为APPClassLoader和ExtClassLoader都是该类的静态内部类 二类加载器加载时其加载类的其他内容都是由该类加载器加载

        System.out.println(System.getProperty("java.system.class.loader"));//文件指定路径

        System.out.println(MyTest23.class.getClassLoader());

        System.out.println(Test16.class.getClassLoader());
        
		System.out.println(ClassLoader.getSystemClassLoader());//etSystemClassLoader静态方法 类名.获取系统类加载器
    }
}

在ClassLoader的doc文档中


  * <p> If the system property "<tt>java.system.class.loader</tt>" is defined
     * when this method is first invoked then the value of that property is
     * taken to be the name of a class that will be returned as the system
     * class loader.  The class is loaded using the default system class loader
     * and must define a public constructor that takes a single parameter of
     * type <tt>ClassLoader</tt> which is used as the delegation parent.  An
     * instance is then created using this constructor with the default system
     * class loader as the parameter.  The resulting class loader is defined
     * to be the system class loader.

如果系统属性java.system.class.loader”定义首次调用此方法时(如果java.system.class.loader指定的路径为null未定义时 会去使用AppClassLoader作为系统类加载器,当定义了系统属性时系统会让默认的AppClassLoader这个类加载器去加载即将成为系统类加载这个自定义类加载器),该属性的值(方法名)将作为系统类加载器的名字。类会使用默认的系统类加载器装入的必须定义一个公共构造函数,该构造函数只接受一个参数类型类加载器,用作委托父类(ClassLoader的一个成员变量每个子类都有)然后使用默认系统类加载器作为这个构造函数参数。此时自定义的类加载器将成为系统类装入器。

当我们直接run时


null
null
null 此时java.system.class.loader 并没有指定自定义路径下的类为系统加载类
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2

根据文档修改默认的系统类加载器时必须在自定义的类加载器中编写一个构造函数该构造函数只接受一个参数类型类装入器,用作委托父类。在Test16中加入对应的构造函数 重新编译

//新加一个构造方法
    public Test16(ClassLoader parent) {
        super(parent);
    }

在控制台输入 将java.system.class.loader 指定的类加载器设置为com.example.demo.com.jvm.Test16 并加载com.example.demo.com.jvm.MyTest23

java -Djava.system.class.loader=com.example.demo.com.jvm.Test16 com.example.demo.com.jvm.MyTest23
null
null
com.example.demo.com.jvm.Test16
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2 //此时AppClassLoader是自定义系统类加载器的父类 父类可以加载 就直接输出
com.example.demo.com.jvm.Test16@7852e922 //此时系统类加载器为我们自定义的类加载器

getSystemClassLoader()源码分析


问题1,为什么自带的类加载器加载指定位置?
问题1,为什么系统类加载器是扩展类加载器的子类?

doc文档


 Returns the system class loader for delegation.  This is the default
     * delegation parent for new <tt>ClassLoader</tt> instances, and is
     * typically the class loader used to start the application.
     *
     * <p> This method is first invoked early in the runtime's startup
     * sequence, at which point it creates the system class loader and sets it
     * as the context class loader of the invoking <tt>Thread</tt>.
     *
     * <p> The default system class loader is an implementation-dependent
     * instance of this class.
     *
     * <p> If the system property "<tt>java.system.class.loader</tt>" is defined
     * when this method is first invoked then the value of that property is
     * taken to be the name of a class that will be returned as the system
     * class loader.  The class is loaded using the default system class loader
     * and must define a public constructor that takes a single parameter of
     * type <tt>ClassLoader</tt> which is used as the delegation parent.  An
     * instance is then created using this constructor with the default system
     * class loader as the parameter.  The resulting class loader is defined
     * to be the system class loader.
     *
  
  • 返回用于委托的系统类装入器。他是新的(自定义)类加载器的默认委托双亲(父类),并且是通常用于启动应用程序的类加载器。(main方法)
  • 此方法首先在程序运行时启动时很早被调用,此时创建并设置系统类加载器;并设置为:将调用该方法线程的上下文类加载器。
  • 默认的系统类加载器是依赖于实现的这个类的实例。如果系统属性java.system.class.loader”定义首次调用此方法时(如果java.system.class.loader指定的路径为null未定义时
  • 会去使用AppClassLoader作为系统类加载器,当定义了系统属性时系统会让默认的AppClassLoader这个类加载器去加载即将成为系统类加载这个自定义类加载器),该属性的值(方法名)将作为系统类加载器的名字。类会使用默认的系统类加载器装入的必须定义一个公共构造函数,该构造函数只接受一个参数类型类加载器,用作委托父类(ClassLoader的一个成员变量每个子类都有)然后使用默认系统类加载器作为这个构造函数参数。此时自定义的类加载器将成为系统类装入器。

ClassLoader类中


  //获取系统类加载器
  @CallerSensitive
    public static ClassLoader getSystemClassLoader() {
    	//初始化系统类加载器
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        //获取安全管理器
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }


   //初始化系统类加载器
   private static synchronized void initSystemClassLoader() {
   		//如果系统类加载器没有被设置
        if (!sclSet) {
        	//系统类加载器没有没有被设置又不为空 矛盾 就抛异常
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
             //Launcher.getLauncher 获取该实例(解析在下)
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
            	//创建成功
                Throwable oops = null;
                //把Launcher 中的系统类加载器付给当前类的类加载器
                scl = l.getClassLoader();
                try {
                //获取类加载器对象(有可能是appClassLoader或者自定义类加载器)
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));//传入系统类加载器作为父加载器
                } catch (PrivilegedActionException pae) {
                    oops = pae.getCause();
                    if (oops instanceof InvocationTargetException) {
                        oops = oops.getCause();
                    }
                }
                if (oops != null) {
                    if (oops instanceof Error) {
                        throw (Error) oops;
                    } else {
                        // wrap the exception
                        throw new Error(oops);
                    }
                }
            }
            //此时系统类加载器设置完成
            sclSet = true;
        }
    }

参数


	//委托双亲 
   private final ClassLoader parent;
   // 系统类加载器
    // @GuardedBy("ClassLoader.class")
    private static ClassLoader scl;

    // 如果设置了系统类加载器的话 sclSet 为true
    // @GuardedBy("ClassLoader.class")
    private static boolean sclSet;

SystemClassLoaderAction类分析


class SystemClassLoaderAction
    implements PrivilegedExceptionAction<ClassLoader> {
    private ClassLoader parent;

    SystemClassLoaderAction(ClassLoader parent) {
        this.parent = parent;
    }

    public ClassLoader run() throws Exception {
    //获取java.system.class.loader 系统属性
        String cls = System.getProperty("java.system.class.loader");
        if (cls == null) {
        //如果改系统属性没被设置返回 appClassLoader
            return parent;
        }
		//如果自定义了系统类加载器
		//获取cls名的Class参数为(ClassLoader.class)的构造函数 
		//Class.forName 加载cls对应的二进制名 并使用父类(parent)初始化(true)
        Constructor<?> ctor = Class.forName(cls, true, parent)
            .getDeclaredConstructor(new Class<?>[] { ClassLoader.class });
        //将父类加载器即系统类加载器传给了自定义类加载器
        //所以当自定义类加载器在设置完时是由appClassLoader去加载的
        //获取自定义的ClassLoader类
        ClassLoader sys = (ClassLoader) ctor.newInstance(
        //为什么自定义类加载器必须要编写一个接收ClassLoader构造方法定义个父类参数的原因
            new Object[] { parent });
            
        Thread.currentThread().setContextClassLoader(sys);
        //返回自定义类加载器
        return sys;
    }
}

Launcher

getLauncher内容讲解

进入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 {
        	//创建应用类加载器实例,并将扩展类加载器作为父类 并将其作为Launcher的一个成员变量
        	//为什么不把扩展类加载器也作为成员变量 没必要 因为 应用类加载器作为其子类可以获取到
            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);
        }

    }

getExtClassLoader() 获取已经加载的扩展类加载器

 static class ExtClassLoader extends URLClassLoader {
 //创建的扩展类加载器限制java.ext.dirs地区class文件
        public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
        	//获取指定文件数组  System.getProperty("java.ext.dirs");
            final File[] var0 = getExtDirs();

            try {
                return (Launcher.ExtClassLoader)AccessController.doPrivileged(
                //返回时判断是否有权限去执行该操作
                new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
                    public Launcher.ExtClassLoader run() throws IOException {
                        //获取路径长度
                        int var1 = var0.length;
						//遍历每一个路径获取对应路径内容
                        for(int var2 = 0; var2 < var1; ++var2) {
                            MetaIndex.registerDirectory(var0[var2]);
                        }
						//返回扩展类对象给调用端
                        return new Launcher.ExtClassLoader(var0);
                    }
                });
            } catch (PrivilegedActionException var2) {
                throw (IOException)var2.getException();
            }
        }



从java.class.path下加载指定内容
getAppClassLoader(final ClassLoader var0) 获取系统类加载器
为什么扩展类加载器是系统类加载器父类? 解答如下

  static class AppClassLoader extends URLClassLoader {
        final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);

        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);
                }
            });
        }

        AppClassLoader(URL[] var1, ClassLoader var2) {
        	//调用父类完成双亲委托 将扩展类加载器作为父类
            super(var1, var2, Launcher.factory);
            this.ucp.initLookupCache(this);
        }
  static class AppClassLoader extends URLClassLoader {
        final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);

        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);
                }
            });
        }

        AppClassLoader(URL[] var1, ClassLoader var2) {
        	//调用父类完成双亲委托 将扩展类加载器作为父类
            super(var1, var2, Launcher.factory);
            this.ucp.initLookupCache(this);
        }

分析Class.forName


public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)

  • 返回给定名字(String name)的类或接口的Class对象。用给定的( ClassLoader loader)的类加载器去加载这个方法试图定位、加载和链接类或接口。指定的类加载器用于加载类或接口。如果参数loader为null,类通过bootstrap类加载器加载。这个类类只有在initialize参数是true,尚未被初始化的情况下才会被初始化
  • 如果{String name}表示原始类型或void,则尝试将被用来在其未命名的包中寻找一个用户定义的类 name是{String name}。因此,这个方法不能用于获取表示原生类型或void的任何 Class对象。
  • 如果{String name}表示一个数组类,则组件类型(jvm)为加载了数组类,但没有初始化。例如,在一个实例方法中的表达式: Class.forName (" Foo ")

Class<?> caller = Reflection.getCallerClass();获取调用者的类加载器


//该会将调用者的类加载器去加载className的类
 public static Class<?> forName(String className)
                throws ClassNotFoundException {
        //获取调用者的Class对象 单参默认使用调用者的类加载器
        Class<?> caller = Reflection.getCallerClass();
        //返回Class 使用C++实现
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

相当于:Class.forName(“foo”,true, this.getClass.getClassLoader()) 这个方法是使用 自定义的类加载器

无指定加载器使用当前加载这个类的加载器去加载这个类,如果指定了类加载器就会使用指定的类加载器去加载

注意此方法抛出与加载、链接或初始化相关的错误按照的第12.2、12.3和12.4节的规定进行初始化Java语言规范。注意,这个方法不会检查被请求的类是否存在调用者可以访问*。

String name:指定类完整的限定名

boolean initialize:是否初始化

ClassLoader loader:用于去加载这个类的类加载器

源代码

  public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
        Class<?> caller = null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // Reflective call to get caller class is only needed if a security manager
            // is present.  Avoid the overhead of making this call otherwise.
            //获取调用forname方法的类的Class对象
            caller = Reflection.getCallerClass();
            if (sun.misc.VM.isSystemDomainLoader(loader)) {
            	//获取调用forname方法的类的Class对象的类加载器
                ClassLoader ccl = ClassLoader.getClassLoader(caller);
                if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
                	//安全检查
                    sm.checkPermission(
                        SecurityConstants.GET_CLASSLOADER_PERMISSION);
                }
            }
        }
        //c++代码
        return forName0(name, initialize, loader, caller);
    }

相关推荐

如何为MySQL服务器和客户机启用SSL?

用户想要与MySQL服务器建立一条安全连接时,常常依赖VPN隧道或SSH隧道。不过,获得MySQL连接的另一个办法是,启用MySQL服务器上的SSL封装器(SSLwrapper)。这每一种方法各有其...

Mysql5.7 出现大量 unauthenticated user

线上环境mysql5.7突然出现大量unauthenticateduser,进mysql,showprocesslist;解决办法有:在/etc/hosts中添加客户端ip,如192.16...

MySQL 在 Windows 系统下的安装(mysql安装教程windows)

更多技术文章MySQL在Windows系统下的安装1.下载mysql和Framework链接链接:百度网盘请输入提取码提取码:6w3p双击mysql-installer-communit...

MySql5.7.21.zip绿色版安装(mysql数据库绿色版安装)

1、去网上下载满足系统要求的版本(mysql-5.7.21-winx64.zip)2、直接解压3、mysql的初始化(1)以管理员身份运行cmd,在mysql中的bin目录下shift+右键-在...

MySQL(8.0)中文全文检索 (亲测有效)

在一堆文字中找到含有关键字的应用。当然也可以用以下语句实现:SELECT*FROM<表名>WHERE<字段名>like‘%ABC%’但是它的效率太低,是全盘扫描。...

新手教程,Linux系统下MySQL的安装

看了两三个教程。终于在哔哩哔哩找到一个简单高效的教程,成功安装,up主名叫bili逍遥bili,感兴趣可以去看看。下面这个是我总结的安装方法环境:CentOS764位1.下载安装包,个人觉得在...

麒麟服务器操作系统安装 MySQL 8 实战指南

原文连接:「链接」Hello,大家好啊,今天给大家带来一篇麒麟服务器操作系统上安装MySQL8的文章,欢迎大家分享点赞,点个在看和关注吧!MySQL作为主流开源数据库之一,被广泛应用于各种业务...

用Python玩转MySQL的全攻略,从环境搭建到项目实战全解析

这是一篇关于“MySQL数据库入门实战-Python版”的教程,结合了案例实战分析,帮助初学者快速掌握如何使用Python操作MySQL数据库。一、环境准备1.安装Python访问Pytho...

安装MySQL(中标麒麟 安装mysql)

安装MySQL注意:一定要用root用户操作如下步骤;先卸载MySQL再安装1.安装包准备(1)查看MySQL是否安装rpm-qa|grepmysql(2)如果安装了MySQL,就先卸载rpm-...

Mysql最全笔记,快速入门,干货满满,爆肝

目录一、MySQL的重要性二、MySQL介绍三、软件的服务架构四、MySQL的安装五、SQL语句六、数据库相关(DDL)七、表相关八、DML相关(表中数据)九、DQL(重点)十、数据完...

MAC电脑安装MySQL操作步骤(mac安装mysqldb)

1、在官网下载MySQL:https://dev.mysql.com/downloads/mysql/根据自己的macOS版本,选择适配的MySQL版本根据自己需求选择相应的安装包,我这里选择macO...

mysql主从(mysql主从切换)

1、本章面试题什么是mysql主从,主从有什么好处什么是读写分离,有什么好处,使用mycat如何实现2、知识点2.1、课程回顾dubboORM->MVC->RPC->SOApro...

【linux学习】以MySQL为例,带你了解数据库

做运维的小伙伴在日常工作中难免需要接触到数据库,不管是MySQL,mariadb,达梦还是瀚高等其实命令都差不多,下面我就以MySQL为例带大家一起来了解下数据库。有兴趣的小伙伴不妨评论区一起交流下...

玩玩WordPress - 环境简介(0)(玩玩网络科技有限公司)

简介提到开源博客系统,一般都会直接想到WordPress!WordPress是使用PHP开发的,数据库使用的是MySQL,一般会在Linux上运行,Nginx作为前端。这时候就需要有一套LNMP(Li...

服务器常用端口都有哪些?(服务器端使用的端口号范围)

下面为大家介绍一下,服务器常用的一些默认端口,以及他们的作用:  21:FTP服务所开放的端口,用于上传、下载文件。  22:SSH端口,用于通过命令行模式远程连接Linux服务器或vps。  23:...

取消回复欢迎 发表评论: