本章内容是对《深刻理解Java虚拟机:JVM高级特性和最佳实践》的理解和归纳。java
在上文中咱们已经讲了类的加载机制,这一章的主角就是类加载器和双亲委派模型了。mysql
在Java虚拟机中,类加载器十分重要。每个类的加载,都须要经过一个类的加载器。可是若是咱们建立一个属于本身的类加载器,这个时候会出现一个什么样的状况呢? 接下来,咱们用代码来进行验证测试。sql
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);
}
}
复制代码
equals()
、
isAssignableFrom()
、
isInstance()
、
instanceof
。若是要相同,除非你直接在java源码上动手脚。
第一个问题:为何须要这个模型? 其实这个模型的提出,就是为了解决类加载器可能不出现不一样的问题。由于即使是相同的class
,由不一样的类加载器加载时,结果就是不一样的。api
双亲委派的工做流程很是简单,这就跟以前文章里的Android的事件分发机制同样,向上传递,由上一层的加载器先行尝试消费,若是上一层没法完成这个任务,那么子加载器就要由本身动手完成。ide
- 启动类加载器:负责加载/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;
}
}
复制代码
讲完了他的工做原理,天然就要知道,他可以如何被破坏的了。post
第二个问题,为何要破坏双亲委派? 拿最简单的例子,在上文中咱们,提到过各个资源的加载范围,可是Driver
做为后来才加入的一个接口,他的不少api是由第三方服务商开发的。那么这个时候,破坏双亲委派就有了他的用武之地了,固然这只是他的用处之一。学习
下面来介绍,他是如何破坏双亲委派的。 先看看咱们平时都是怎么用的。(固然这是很基础的写法了,由于如今池的概念加深,因此不少事情都已经被封装了。)测试
String url = "jdbc:mysql://localhost:3306/db";
Connection conn = DriverManager.getConnection(url, "root", "root");
复制代码
上面很明显就能看出这件事情就是关于DriverManager
展开的了。ui
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()
,看到这个函数了吗,我在上文提到过,这个函数咱们得到的类加载器将会是应用程序类加载器。也就是说咱们的任务不会再向上传递了,到头就是到了应用程序类加载器这个位置,那么双亲委派模型也就破坏了。
以上就是打破双亲委派的方法之一的介绍了。
为何说咱们调用的是应用程序类加载器呢? 接下来直接从源码来解析了。 首先就是调用getSystemClassLoader()
这个函数了
这张图里咱们只用关注圈红的函数。
而后在initSystemClassLoader()
函数中调用了一个Launcher
的类。
而Launcher
整个类的建立,想来读者也已经看到loader
这个变量了,经过getAppClassLoader()
这个函数所建立的loader
也就是咱们口中所说的应用程序类加载器了。
以上就是个人学习成果,若是有什么我没有思考到的地方或是文章内存在错误,欢迎与我分享。
相关文章推荐: