来谈一谈类加载器。
java的class文件都是通过类加载器被加载到jvm的方法区中,供内存调用。
那么类加载器究竟是如何加载一个class文件的?
我们先要找个class文件看看,不管你们看没看懂,反正我是看不懂。
但是jvm看得懂,因为其中存在jvm可以识别的文件标示,而且能通过jvm内部的特定规则进行编译。
但是classloader不负责管这个类是否可以运行,类是否能运行,看的是Execution Engine。
那么jvm是不是只有一个classloader呢?
虚拟机自带的有三个:bootstrap、extension和AppClassLoader。
bootstrap负责的是最基础的一些类的加载。
这些类在jre/lib/rt.jar中。
可以看到,里面有个包叫java.lang,下面就有了很多我们平时常用的类。
bootstrap的作用就是加载这些类。
那么Extension加载了什么呢?
是jre/lib/ext下的所有jar包。
那我们平时自定义的那些jar包和类在哪加载呢?
自然就是最后的AppClassLoader。
同样,用户也可以自定义加载器。
那么,我们也可以经常听到类加载器有一个叫“双亲委派机制”的东西,这是什么呢?
简单地说,就是当一个类加载器收到了加载请求时,它首先不会自己加载这个类,而是把它委派给父类去完成。如果说父类没有能够成功加载这个请求,那么才会交给子类继续完成。
举个例子演示一下。
我们知道java包下有java.lang.String。
按照之前介绍的规则,它应该由bootstrap的类加载器来加载。
那如果我们手动创建java.lang包,并且创建一个String类,那么理论上因为是自己创建的类,应该由AppClassLoader创建,那事实上呢?
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
}
}
我们创建如下的类,通过Main方法看一下对应的classloader。
但是运行结果是这样的。
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application
这说明,类加载器并没有加载我们后面定义的那个类。
而我们也知道String类下是没有main方法的。
所以很明显,他的加载从父类开始,因为在父类已经找到了java.lang.String,所以就直接加载了这个类,而没有加载我们定义的类。
我们把包名和类名修改了试试。
package java2.lang2;
public class String2 {
public static void main(String[] args) {
System.out.println(String2.class.getClassLoader());
}
}
结果就不一样了,输出的是sun.misc.Launcher$AppClassLoader@18b4aac2。
可以看到这次是通过AppClassLoader进行了加载,因为这次的包在rt.jar下不再存在。
这就是双亲委派机制。
这样做有什么好处呢?
比如加载rt.jar 包中的类 java.lang.String,不管哪个加载器加载这个类,最终都委派给了顶层加载器bootstrap进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 String对象。
这便是类加载器最基本的一些机制。