关于 ClassLoader 的一些疑问

2020-04-19 16:33:30 +08:00
 yangyuhan12138

众所周知

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 从而过滤调了我们自己加载的类,而我们自己加载的类则只能通过反射来调用

2951 次点击
所在节点    Java
12 条回复
kaedea
2020-04-19 16:35:15 +08:00
线程 ClassLoader 了解一下
yangyuhan12138
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
2020-04-19 20:39:58 +08:00
果然很可疑阿 haha 有讲与没讲差不多果然才是对的
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
2020-04-19 21:19:36 +08:00
@james122333 啊?
@sioncheng 这些我都看了呀...疑问还是没解决
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
2020-04-19 21:49:32 +08:00
你想问是的 ClassLoader A 加载了 Class A,为什么在没有显式声明使用 ClassLoader A 的情况下就可以加载 Class A 依赖的 Class B 吗
yangyuhan12138
2020-04-19 22:12:25 +08:00
@fantastM 不是呀....我的意思是同名 class 不同 classloader 加载进 jvm 的 在我们直接 new 的时候到底是使用的那个 classloader 加载进来的 class
pursuer
2020-04-19 22:14:49 +08:00
1 、java 不需要保证类是从哪个 classloader 加载的
2 、并不是只能通过反射调用,你甚至可以自定义 classloader 破坏双亲委派替换掉一个本来已经加载的类去
fantastM
2020-04-19 22:23:10 +08:00
「我们直接 new 的时候」这时候程序已经运行在一个被 ClassLoader 加载的类里了,默认就会用这个 ClassLoader 去加载当前类依赖的还没有被加载的其它类。
yangyuhan12138
2020-04-19 23:29:54 +08:00
@fantastM 已加载的也会优先使用当前 classloader 加载的 我刚试的就是这样
yangyuhan12138
2020-04-19 23:31:20 +08:00
@pursuer 我现在不就是这样做的吗 加载一个已经存在的类 只是 classloader 不同

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/663999

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX