本章内容是对《深入理解Java虚拟机:JVM高级特性和最佳实践》的理解和概括。
前言
在上文中我们已经讲了类的加载机制,这一章的主角就是类加载器和双亲委派模型了。
类加载器
在Java虚拟机中,类加载器十分重要。每一个类的加载,都需要通过一个类的加载器。但是如果我们创建一个属于自己的类加载器,这个时候会出现一个什么样的情况呢?
接下来,我们用代码来进行验证测试。
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
// 由自己的类加载器创建对象
Object obj = myLoader.loadClass("XuNiJi.ClassLoaderTest").newInstance();
// 由系统提供的类加载器加载创建对象
Object obj1 = ClassLoader.getSystemClassLoader().loadClass("XuNiJi.ClassLoaderTest").newInstance();
System.out.println(obj instanceof ClassLoaderTest);
System.out.println(obj1 instanceof ClassLoaderTest);
}
}
从这里想来已经能够看出了,由一个类加载器统一创建的类,才存在可比性。因为类加载器是拥有独立的类名称空间的。更简单的说,就像上面的例子,如果不使用Java虚拟机提供的类加载器,你就会失去一大部分功能,比如
equals()
、isAssignableFrom()
、isInstance()
、instanceof
。如果要相同,除非你直接在java源码上动手脚。
双亲委派模型
第一个问题:为什么需要这个模型?
其实这个模型的提出,就是为了解决类加载器可能不出现不同的问题。因为即便是相同的class
,由不同的类加载器加载时,结果就是不同的。
工作原理
双亲委派的工作流程非常简单,这就跟之前文章里的Android的事件分发机制一样,向上传递,由上一层的加载器先行尝试消费,如果上一层无法完成这个任务,那么子加载器就要由自己动手完成。
- 启动类加载器:负责加载/lib下的类。
- 扩展类加载器:负责加载/lib/ext下的类。
- 系统类加载器/应用程序类加载器:
ClassLoader.getSystemClassLoader
返回的就是它。
通过上图我们可以知道,子加载器不断的给上一层加载器传递加载请求,那么这个时候启动类加载器势必是接受到过全部的加载请求的。如果不信,我们就用源码来证明。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 判断Class是否被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父类抛出ClassNotFoundException
// 说明父类无法完成加载
}
// 这个时候c依旧为null,说明父类加载不了
// 那没有办法,只能子加载器自己效劳了
if (c == null) {
long t1 = System.nanoTime();
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
讲完了他的工作原理,自然就要知道,他能够如何被破坏的了。
破坏双亲委派模型
第二个问题,为什么要破坏双亲委派?
拿最简单的例子,在上文中我们,提到过各个资源的加载范围,但是Driver
作为后来才加入的一个接口,他的很多api是由第三方服务商开发的。那么这个时候,破坏双亲委派就有了他的用武之地了,当然这只是他的用处之一。
下面来介绍,他是如何破坏双亲委派的。
先看看我们平时都是怎么用的。(当然这是很基础的写法了,因为现在池的概念加深,所以很多事情都已经被封装了。)
String url = "jdbc:mysql://localhost:3306/db";
Connection conn = DriverManager.getConnection(url, "root", "root");
上面很明显就能看出这件事情就是关于DriverManager
展开的了。
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
这里根据前一章的内容先要对DriverManager
进行初始化,也就是调用了一个loadInitialDrivers()
函数。
private static void loadInitialDrivers() {
.....
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);// 1
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
.....
}
从这一小段中,我们关注注释1
能够知道他专门去访问了一个ServiceLoader
的类,点进去之后我们能够发现这么三段代码。
// 1
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
// 2
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
return new ServiceLoader<>(service, loader);
}
// 3
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
由1 --> 2 --> 3的顺序循序渐进,你是否已经和我关注到一个问题了!!
ClassLoader.getSystemClassLoader()
,看到这个函数了吗,我在上文提到过,这个函数我们获得的类加载器将会是应用程序类加载器。也就是说我们的任务不会再向上传递了,到头就是到了应用程序类加载器这个位置,那么双亲委派模型也就破坏了。
以上就是打破双亲委派的方法之一的介绍了。
溯源ClassLoader.getSystemClassLoader()
为什么说我们调用的是应用程序类加载器呢?
接下来直接从源码来解析了。
首先就是调用getSystemClassLoader()
这个函数了
这张图里我们只用关注圈红的函数。
然后在initSystemClassLoader()
函数中调用了一个Launcher
的类。
而Launcher
整个类的创建,想来读者也已经看到loader
这个变量了,通过getAppClassLoader()
这个函数所创建的loader
也就是我们口中所说的应用程序类加载器了。
以上就是我的学习成果,如果有什么我没有思考到的地方或是文章内存在错误,欢迎与我分享。