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 从而过滤调了我们自己加载的类,而我们自己加载的类则只能通过反射来调用
经过一下午的思考 我觉得我已经找到答案了
我的问题其实简单描述出来就是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
1
kaedea 2020-04-19 16:35:15 +08:00 via Android
线程 ClassLoader 了解一下
|
2
yangyuhan12138 OP @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 中的类加载器来加载的? |
3
james122333 2020-04-19 20:39:58 +08:00
果然很可疑阿 haha 有讲与没讲差不多果然才是对的
|
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 |
5
yangyuhan12138 OP |
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 这个类的代码就好了。 |
7
fantastM 2020-04-19 21:49:32 +08:00
你想问是的 ClassLoader A 加载了 Class A,为什么在没有显式声明使用 ClassLoader A 的情况下就可以加载 Class A 依赖的 Class B 吗
|
8
yangyuhan12138 OP @fantastM 不是呀....我的意思是同名 class 不同 classloader 加载进 jvm 的 在我们直接 new 的时候到底是使用的那个 classloader 加载进来的 class
|
9
pursuer 2020-04-19 22:14:49 +08:00
1 、java 不需要保证类是从哪个 classloader 加载的
2 、并不是只能通过反射调用,你甚至可以自定义 classloader 破坏双亲委派替换掉一个本来已经加载的类去 |
10
fantastM 2020-04-19 22:23:10 +08:00
「我们直接 new 的时候」这时候程序已经运行在一个被 ClassLoader 加载的类里了,默认就会用这个 ClassLoader 去加载当前类依赖的还没有被加载的其它类。
|
11
yangyuhan12138 OP @fantastM 已加载的也会优先使用当前 classloader 加载的 我刚试的就是这样
|
12
yangyuhan12138 OP @pursuer 我现在不就是这样做的吗 加载一个已经存在的类 只是 classloader 不同
|