通读这篇文章你会知道如何回答以下问题:
- Java自带的三大加载器加载的jar位置都是在哪里?
- 三大加载器之间的关系是怎么样的? 在代码中是如何体现的?
- 双亲委派模型是什? 代码中如何体现这种模式的应用? 这种模式的不足是什么?
- 上下文加载器存在的作用是什么?应用的场景有哪些?
java三大加载器加载的jar位置
知道每个加载器加载什么位置的jar,这对后面分析委托机制会起到作用。
Java语言自带的有三个类加载器
-
Bootstrap CLassloder
-
Extention ClassLoader
-
AppClassLoader
以下是输出各个加载器的加载jar的位置,这些路径可以通过虚拟机参数进行修改。public static void testClassLoader() { System.out.println("BootstrapClassLoader:"); String property = System.getProperty("sun.boot.class.path"); Arrays.stream(property.split(";")).forEach(System.out::println); System.out.println("ExtClassLoader :"); property = System.getProperty("java.ext.dirs"); Arrays.stream(property.split(";")).forEach(System.out::println); System.out.println("AppClassLoader :"); property = System.getProperty("java.class.path"); Arrays.stream(property.split(";")).forEach(System.out::println); }
- Bootstrap CLassloder 加载位置如下:
JDK\jdk1.8\jre\lib\resources.jar
JDK\jdk1.8\jre\lib\rt.jar
JDK\jdk1.8\jre\lib\sunrsasign.jar
JDK\jdk1.8\jre\lib\jsse.jar
JDK\jdk1.8\jre\lib\jce.jar
JDK\jdk1.8\jre\lib\charsets.jar
JDK\jdk1.8\jre\lib\jfr.jar
JDK\jdk1.8\jre\classes - Extention ClassLoader加载位置如下:
C:\Program Files\Java\jre1.8.0_91\lib\ext;C:\Windows\Sun\Java\lib\ext; - AppClassLoader 加载位置如下:
就是你项目配置的jar路径以及工程生成的classes的位置,如maven会是 target\classes,或者bin目录; - 这些路径的可以从sun.misc.Launcher类得知;
加载器之间的关系
-
如果类是在boostrapClassLoder下加载,无法获取其加载器
1-1
从报错的情况,因为bootstrapclassloader有加载过rt.jar,这个从前面可以看出来,Integer类是rt.jar里面的类,所以Integer是由bootstrapclassloader进行加载的没错,那为啥是空指针呢?
来一张extclassloader的继承图,AppClassLoader也一样的继承关系
图1
这个空指针我是这么理解的:凡是有boostraploader进行加载的类,都是获取不到此类的加载器,因为Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用; -
ExtClassLoadder的父加载器是null
图2
接下来从源码角度来看下,图2输出的原因(即extClassLoader的父记载器为啥null)
关键从parent方法入手,可以发现在图一的继承关系中找到parent方法是在ClassLoader中如图3,从idea点击过去也可以找到,从方法来看parent是final成员变量,所以找到构造方法,就知道如何它是如何初始化了
图3
从下面的代码可以看出parent初始化有两种方式:
一种是指定ClassLoader;
第二种如果没有指定则通过initSystemClassLoader方法进行初始化,这个方法获取classLoader方式是从Launcher类的getClassLoader方法获取的;protected ClassLoader(ClassLoader parent) { this(checkCreateClassLoader(), parent); } protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); } public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); if (scl == null) { return null; } SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkClassLoaderPermission(scl, Reflection.getCallerClass()); } return scl; } private static synchronized void initSystemClassLoader() { if (!sclSet) { if (scl != null) throw new IllegalStateException("recursive invocation"); sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); if (l != null) { Throwable oops = null; scl = l.getClassLoader(); try { scl = AccessController.doPrivileged( new SystemClassLoaderAction(scl)); } catch (PrivilegedActionException pae) { oops = pae.getCause(); if (oops instanceof InvocationTargetException) { oops = oops.getCause(); } } if (oops != null) { if (oops instanceof Error) { throw (Error) oops; } else { // wrap the exception throw new Error(oops); } } } sclSet = true; } }
Launcher类的classLoader方法初始化如下:
从代码可知Launcher.getClassLoader就是获取appclassLoader,意味着一个类的父加载器如果没有指定,则是默认就是AppClassLoader。
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if(var2 != null) {
SecurityManager var3 = null;
if(!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
;
} catch (InstantiationException var6) {
;
} catch (ClassNotFoundException var7) {
;
} catch (ClassCastException var8) {
;
}
} else {
var3 = new SecurityManager();
}
if(var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
public ClassLoader getClassLoader() {
return this.loader;
}
但是这个还是无法解释extClassLoader的父加载器是null,请看Laucher类中ExtClassLoader类的构造法方方法,指定的父加载器就是null,所以从parent变量的获取追踪到extClassloader是由于指定null父加载器,所以导致extClassLoader获取父加载器是null,
而AppClassLoader获取父加载器是extClassLoader,是因为指定了extClassLoader为自己的父加载器;
appclassLoader构造方法
-
从源码角度理解双亲委托机制以及boostrapClassLoader为啥可以作为extClassLoader的父加载器
双亲委托是什么,看过博主的文章就知道了,一句话概括
委托是从下向上,然后具体查找过程却是自上至下
从ClassLoader的loadClass可以明白双亲委托机制过程,同时知道如果自定义的ClassLoader是覆盖findClass,而不是loadClass,采用这种方式进行加载可以避免java核心api中定义的类型被自定义的加载器加载,从而出现多个
ClassLoadere-loadClass
-
自定义加载器,自己也写个,跟博主一样,然后调试了一遍,对加载过程再熟悉一遍
public class MyClassLoader extends ClassLoader{ private String filePath; MyClassLoader(String filePath) { this.filePath = filePath; } @Override public Class<?> findClass(String name) throws ClassNotFoundException { int i = name.lastIndexOf(".") +1; String s = name.substring(i) + ".class"; File file = new File(filePath, s); try { FileInputStream is = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len = 0; try { while ((len = is.read()) != -1) { bos.write(len); } } catch (IOException e) { e.printStackTrace(); } byte[] data = bos.toByteArray(); is.close(); bos.close(); return defineClass(name,data,0,data.length); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return super.findClass(name); } public static void main(String[] args) throws Exception{ MyClassLoader classLoader = new MyClassLoader("D:\\"); Class<?> aClass = classLoader.loadClass("com.example.Product"); if (aClass != null) { Object o = aClass.newInstance(); Method ok = aClass.getDeclaredMethod("ok"); ok.invoke(o); } } }
-
打破双亲委派模型------引出上下文类加载器
双亲委派模型的加载方式是从底层加载器到顶层加载器进行加载,但是如果顶层加载器要加载来自底层加载器的类时,此时就传统加载模式就无法完成了。
这种情况会出现在SPI使用中,引用一段话来说明SPI加载的情况
SPI机制简介
SPI的全名为Service Provider Interface,主要是应用于厂商自定义组件或插件中。在java.util.ServiceLoader的文档里有比较详细的介绍。简单的总结下java SPI机制的思想:我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块、xml解析模块、jdbc模块等方案。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。
SPI具体约定
Java SPI的具体约定为:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader
。
以下面这段代码来说明双亲委派模型的 逆向加载
String url = "jdbc:mysql://localhost:3306/mysql";
//通过java库获取数据库连接
Connection conn = java.sql.DriverManager.getConnection(url, "root", "123456");
首先调用静态方法必将导致调用者会进行初始化,所以DriverMananger类将初始化,如果类中有静态代码块必将执行,下面是DriverManager类的代码:
静态代码k loadInitialDrivers方法
因为这句Class.forName(DriverName, false, loader)代码所在的类在java.util.ServiceLoader类中,而ServiceLoader.class又加载在BootrapLoader中,因此传给 forName 的 loader 必然不是BootrapLoader,那会是什么加载器呢,就是上下文加载,这个可以从load方法中看出:
load所以这边就可以看出要通过顶层加载器去加载底层加载器的类时,通过上下文加载器实现;
网友评论