V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yangyuhan12138
V2EX  ›  Java

关于 ClassLoader 的一些疑问

  •  1
     
  •   yangyuhan12138 · 2020-04-19 16:33:30 +08:00 · 2928 次点击
    这是一个创建于 1439 天前的主题,其中的信息可能已经有所发展或是发生改变。

    众所周知

    jvm 里 class 的唯一标识是 classloader+包+类
    现在我自定义了一个 classloader

    public class MyClassLoader extends ClassLoader
    

    从外部加载指定类进入 jvm

            MyClassLoader mcl = new MyClassLoader();
            Class<?> clazz = Class.forName("com.yuhan.demo.controller.People", true, mcl);
            obj = clazz.newInstance();
            System.out.println(obj.getClass().getClassLoader());//打印出我们的自定义类加载器
    
    

    这种加载方法应该是使用了双亲委托机制如果 AppClassLoader 已经加载过 People 了则不会重新加载
    控制台输出

    sun.misc.Launcher$AppClassLoader@73d16e93
    

    如果我们将 People.class 从 classpath 中删除放到其他地方,避免 AppClassLoader 直接加载
    则输出

    com.yuhan.demo.controller.MyClassLoader@15db9742
    

    但是如果我们直接这样调用

           mcl = new MyClassLoader();
           aClass = mcl.findClass("com.yuhan.demo.controller.People");
           obj =aClass.newInstance();
           System.out.println(obj.getClass().getClassLoader());//打印出我们的自定义类加载器
    

    则不管 AppClassLoader 是否加载过都会由 MyClassLoader 来加载(相当于绕过了双亲?)

    现在我有个疑问就是我用 findClass 方法加载了一个 jvm 中已经存在的 class(包名类名都相同),相当于 jvm 中就有两个相同的 class 了(jvm 中是允许这样存在的因为 classloader 不同) 但是 java 中使用类的时候只指定了包名和类名并没有指定 classloader 那么 java 是如何保证我 new People 的时候是是用的 AppClassLoader 的 People 而不是 MyClassLoader 的 People 呢

    我的猜测是我们直接使用类的时候 java 加上了默认的 classloader 从而过滤调了我们自己加载的类,而我们自己加载的类则只能通过反射来调用

    第 1 条附言  ·  2020-04-19 22:10:25 +08:00

    经过一下午的思考 我觉得我已经找到答案了
    我的问题其实简单描述出来就是jvm中有两个同名的类但是classloader不一样 以People为例
    在我new People()的时候jvm到底如何决定使用哪个classloader 加载进来的People呢

    下边是我的答案

    之前我认为系统会默认使用AppClassLoader加载的People,看起来也确实是这样的,但是今天下午看DriverManager的源码的时候发现了

      ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
            synchronized(DriverManager.class) {
                // synchronize loading of the correct classloader.
                if (callerCL == null) {
                    callerCL = Thread.currentThread().getContextClassLoader();
                }
            }
    

    这样一段代码 (一楼叫我去看 context classloader 才发现的)
    于是我在想会不会跟调用类的类加载器有关系
    我之前new People的时候main方法所在的类肯定是AppClassLoader加载进来的所以new 的时候选择了AppClassLoader加载的People 于是我做了如下实验
    我定义了People2类 并且在People的构造函数中初始化

        private People2 people2;
        public People() {
            this.people2 = new People2();
            System.out.println(people2.getClass().getClassLoader());
        }
    

    现在再将People和People2用自定义加载器加载进来,加上AppClassLoader加载的相当于现在jvm中有两个People.class和两个People2.class
    然后分别初始化两个类加载器加载的People.class
    看输出
    果然
    自定义加载器加载的People初始化时输出的就是自定义加载器,
    AppClassLoader加载的People初始化时输出的就是AppClassLoader

    12 条回复    2020-04-19 23:31:20 +08:00
    kaedea
        1
    kaedea  
       2020-04-19 16:35:15 +08:00 via Android
    线程 ClassLoader 了解一下
    yangyuhan12138
        2
    yangyuhan12138  
    OP
       2020-04-19 18:54:04 +08:00
    @kaedea
    谢谢大佬的回复,我去看了下 context class loader 并做了如下实验
    ```
    MyClassLoader loader = new MyClassLoader();
    Thread.currentThread().setContextClassLoader(loader);
    obj = new People();
    System.out.println(obj.getClass());
    System.out.println(obj.getClass().getClassLoader());//打印出我们的自定义类加载器
    ```
    输出
    class com.yuhan.demo.controller.People
    sun.misc.Launcher$AppClassLoader@18b4aac2

    开始我以为的是设置了 context class loader 之后,会去取我设置的 classloader load 的 class 来进行实例化,但是好像并不是这样,运行结果依然为 AppClassLoader,如果我想要取 MyClassLoader load 的 People 还是得 Thread.currentThread().getContextClassLoader() 将 MyClassLoader 取出来之后再 loadclass,所以这个地方其实相当于只是多个个线程副本变量而已,如果直接 new People()的 People 还是 AppClassLoader load 的 People
    所以我的问题的答案应该就是我如果不主动指定 classloader 来 loadclass 的话 我们是使的所有类都是由 Java 中的类加载器来加载的?
    james122333
        3
    james122333  
       2020-04-19 20:39:58 +08:00
    果然很可疑阿 haha 有讲与没讲差不多果然才是对的
    sioncheng
        4
    sioncheng  
       2020-04-19 20:49:25 +08:00
    双亲委派模型,Bootstrap ClassLoader /ExtClassLoader/ AppClassLoader https://blog.csdn.net/briblue/article/details/54973413
    Thread Context Class Loader 与 SPI https://blog.csdn.net/liuchangqing123/article/details/52304644
    yangyuhan12138
        5
    yangyuhan12138  
    OP
       2020-04-19 21:19:36 +08:00
    @james122333 啊?
    @sioncheng 这些我都看了呀...疑问还是没解决
    mazai
        6
    mazai  
       2020-04-19 21:28:39 +08:00
    你要找的答案就在 Launcher 这个类里面,首先 JVM 在初始化的时候启动类加载器会首先被虚拟机执行(一段 C++代码),而 Launcher 这个类正是启动类加载器来加载的。

    他有一个全局静态变量 private static Launcher launcher = new Launcher(); 因此 Launcher 的构造函数会被调用,以下我截出几段构造函数的代码你就明白了:
    this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);

    Thread.currentThread().setContextClassLoader(this.loader);

    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();

    更详细的你自己去看 Launcher 这个类的代码就好了。
    fantastM
        7
    fantastM  
       2020-04-19 21:49:32 +08:00
    你想问是的 ClassLoader A 加载了 Class A,为什么在没有显式声明使用 ClassLoader A 的情况下就可以加载 Class A 依赖的 Class B 吗
    yangyuhan12138
        8
    yangyuhan12138  
    OP
       2020-04-19 22:12:25 +08:00
    @fantastM 不是呀....我的意思是同名 class 不同 classloader 加载进 jvm 的 在我们直接 new 的时候到底是使用的那个 classloader 加载进来的 class
    pursuer
        9
    pursuer  
       2020-04-19 22:14:49 +08:00
    1 、java 不需要保证类是从哪个 classloader 加载的
    2 、并不是只能通过反射调用,你甚至可以自定义 classloader 破坏双亲委派替换掉一个本来已经加载的类去
    fantastM
        10
    fantastM  
       2020-04-19 22:23:10 +08:00
    「我们直接 new 的时候」这时候程序已经运行在一个被 ClassLoader 加载的类里了,默认就会用这个 ClassLoader 去加载当前类依赖的还没有被加载的其它类。
    yangyuhan12138
        11
    yangyuhan12138  
    OP
       2020-04-19 23:29:54 +08:00
    @fantastM 已加载的也会优先使用当前 classloader 加载的 我刚试的就是这样
    yangyuhan12138
        12
    yangyuhan12138  
    OP
       2020-04-19 23:31:20 +08:00
    @pursuer 我现在不就是这样做的吗 加载一个已经存在的类 只是 classloader 不同
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3272 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 13:50 · PVG 21:50 · LAX 06:50 · JFK 09:50
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.