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

关于 ClassLoader 的一些疑问

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

    众所周知

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

    public class MyClassLoader extendsClassLoader 

    从外部加载指定类进入 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     Solana     3391 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 04:43 PVG 12:43 LAX 20:43 JFK 23:43
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86