聊聊ClassLoader
来源:互联网 发布:动态矩阵控制 编辑:程序博客网 时间:2024/06/02 04:16
什么是ClassLoader
java程序在编写的时候都是.java文件,但真正去运行的时候都是加载编译后的.class文件,而不是.java文件;一般项目都不会由单个类构成,这涉及到类的依赖,相互协作完成复杂的业务功能,而在程序启动的时候不会一次性加载程序所要用到的所有class文件,而是根据需要,在用到的时候通过类加载器加载到内存中,然后被其他class引用;
ClassLoader是怎样工作的
java默认提供了3个ClassLoader
java提供了3个ClassLoader,用来加载不同的ClassLoader
bootstrap ClassLoader:该加载器是用C++编写实现的,由jvm在启动的时候初始化,主要加载java_home/lib和JAVA_HOME/jre/classes下的核心类库的部分类,通过设置VM options:-verbose:class,可以看到一些rt.jar包下的核心类被加载,输出类似下面,需要注意的是即使将自定义的类打成jar包,该ClassLoader也不认,bootstrap ClassLoader有安全机制能够识别陌生的非原生的类:
[Opened /Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/jre/lib/rt.jar][Loaded java.lang.Object from /Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/jre/lib/rt.jar]...
- ExtClassLoader:负责加载java_home/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库
- ApClassLoader:负责加载用户类路径下(java -classpath或-Djava.class.path变量所指的目录)的类
ClassLoader的双亲委派机制
- 什么是双亲委派机制呢?打个不是很恰当的比喻,一个人突然去世了,没有将遗产进行分割,需要遗产继承,这个人的孙子怎么样才能拿到这份遗产呢,首先要看看这个人的配偶还在不在,如果在,就优先给配偶了,孙子什么也得不到,如果配偶不在,那么需要看看这个人的儿子女儿是否还在,就这样一层一层的条件筛选下来;ClassLoader的双亲委派机制也是这样,比如在这个时候需要加载A类(前提条件,之前没有被加载过),查看当前的ClassLoader有没有parent,如果有,那么交给parent ClassLoader加载,直到parent为null(ClassLoader的parent为null的时候,表示其parent为bootstrap ClassLoader),这个时候调用bootstrap ClassLoader加载;如果parent ClassLoader不能加载指定的类,那么交由自己加载,如果都不能加载,就要报ClassNotFoundException异常了;
- 那么如果A类已经被加载过了呢?其实每个ClassLoader都有自己的缓存,每次loadClass的时候都会先检查当前ClassLoader的缓存,如果查找不到,再通过双亲委派机制进行loadClass;这个过程也有点类似遗产继承,如果遗产已经被分割,那么某份被划分好的遗产的继承就相当于是ClassLoader的缓存中class,不用再去找parent来loadClass了
ClassLoader的双亲委派机制是通过ClassLoader包含一个父ClassLoader(parent)成员来实现的,看下代码,除了bootstrap ClassLoader是用C++编写的,其他的几个ClassLoader(ExtClassLoader,APPClassLoader)都继承ClassLoader,ClassLoader的构造方法有设置parent的逻辑,下边是截取的小段ClassLoader的代码,
public abstract class ClassLoader { private static native void registerNatives(); static { registerNatives(); } // The parent class loader for delegation // Note: VM hardcoded the offset of this field, thus all new fields // must be added *after* it. private final ClassLoader parent; }
ClassLoader的另一个概念——命名空间
- 命名空间其实跟其他的一些命名空间的概念类似,比如java的类,如何确定两个类是同一个类呢,是不是需要包名+类名呢,那ClassLoader的命名空间也类似,需要类的全限定名(即包名+类名)和加载此类的ClassLoader来共同确定,很好理解,同一个类A,由ClassLoader1和ClassLoader2加载是不一样的;而类的访问限定也是通过类加载器来限定的,比如自己定义了一个java.lang.A类,A这个时候是不能访问java.lang包下的某个类的protected成员的,验证这个规则的代码及运行结果如下:
package java.lang;public class TestProtected { public static void main(String[] args) { System.out.println("haha"); Thread thread = new Thread(); try { thread.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } }}
运行结果
Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662) at java.lang.ClassLoader.defineClass(ClassLoader.java:761) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) at java.net.URLClassLoader.access$100(URLClassLoader.java:73) at java.net.URLClassLoader$1.run(URLClassLoader.java:368) at java.net.URLClassLoader$1.run(URLClassLoader.java:362) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:361) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:264) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:123)
看看上面的结果,编译是没有任何问题的,因为这个命名空间是在运行时起作用的,而编译的时候,TestProtected确实跟Thread位于同一个包下,但是在运行的时候抛异常了;
- 双亲委托机制可以避免类加载混乱,比如两个ClassLoader都需要加载java.lang.String,如果不采用双亲委托机制,这样java.lang.String会被两个ClassLoader加载2遍,而由于命名空间的限制,这两个ClassLoader加载的java.lang.String虽然是同一份字节码,但不是相同的,在程序赋值的时候会出现ClassCastException(这个异常经常在类型强制转换的时候出现),那么就很混乱;而采用双亲委托机制,java.lang.String会由bootstrap ClassLoader加载,所有只会加载一次,不会出现ClassCastException的情况
- 有些场景需要打破双亲委托机制,比如tomcat的类加载机制(对于tomcat的类加载机制,后面再仔细研究)
看看源码
ClassLoader最重要的几个方法是:
public Class<?> loadClass(String name) throws ClassNotFoundException
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
protected final Class
protected ClassLoader(ClassLoader parent) { //指定parent ClassLoader,如果parent为null,那么就相当于指定bootstrap ClassLoader为parent this(checkCreateClassLoader(), parent);}protected ClassLoader() { //不带参数的构造函数默认采用AppClassLoader:getSystemClassLoader() this(checkCreateClassLoader(), getSystemClassLoader());}private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; if (ParallelLoaders.isRegistered(this.getClass())) { parallelLockMap = new ConcurrentHashMap<>(); package2certs = new ConcurrentHashMap<>(); domains = Collections.synchronizedSet(new HashSet<ProtectionDomain>()); assertionLock = new Object(); } else { // no finer-grained lock; lock on the classloader instance parallelLockMap = null; package2certs = new Hashtable<>(); domains = new HashSet<>(); assertionLock = this; }}
- 看看加载类的入口:loadClass方法(已删减一些关系不大的代码);注意jdk8跟jdk6loadClass方法已经被重写了,有稍微一点不同,但大体上是一致的
public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false);}protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ synchronized (getClassLoadingLock(name)) { // 首选,查找以及被加载的class Class<?> c = findLoadedClass(name); if (c == null) { //如果没有在缓存中找到需要加载的class,通过双亲委托机制加载 try { if (parent != null) { //如果有parent,那么通过parent加载 c = parent.loadClass(name, false); } else { //如果没有parent,那么通过bootstrap ClassLoader加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } //如果双亲委托机制也加载不到,则通过当前ClassLoader加载,加载不到就抛出异常 if (c == null) { c = findClass(name); } } //很多时候resolve都是false,但会在以后实际需要用的时候进行resolve,即延迟resolve if (resolve) { //解析装载类:验证Class以确保类装载器格式和行为正确;准备后续步骤所需的数据结构;解析所引用的其他类。 resolveClass(c); } return c; }}
- 在jdk8的源码中,有一行获取锁的过程:getClassLoadingLock(name),而且这个过程被加锁了,这个代码在jdk6中是没有的,这个应该是jdk8改进的地方吧,来看看getClassLoadingLock方法
protected Object getClassLoadingLock(String className) { Object lock = this; //parallelLockMap在构造函数中进行初始化:ConcurrentHashMap if (parallelLockMap != null) { Object newLock = new Object(); lock = parallelLockMap.putIfAbsent(className, newLock); if (lock == null) { lock = newLock; } } return lock;}
从前面ClassLoader的构造方法中看出,如果当前的加载器具备并行能力,就new一个ConcurrentHashMap;从上面的getClassLoadingLock看出,如果parallelLockMap为null,说明不具备并行能力,那么直接返回this,也就是对this进行同步操作;如果具备并行能力,那么需要进行锁操作,如果name对应的key在parallelLockMap有lock存在,那么返回存在的lock,否则返回重新new的lock,这样就实现了并发的同步控制
自定义ClassLoader
- 看到defineClass方法是final,所已不能重写defineClass方法,直接用ClassLoader提供的就可以了,而loadClass和findClass都是非final的,可以自定义,如果有需要不采用双亲委托机制,那么在重写loadClass的时候将双亲委托机制改写掉就好了,通常情况下自定义ClassLoader主要通过重写findClass方法来实现
- 聊聊ClassLoader
- 聊聊ClassLoader与jdbc的关系(contextClassLoader)
- 聊聊
- 聊聊
- 聊聊
- 聊聊
- classLoader
- classloader
- ClassLoader
- ClassLoader
- ClassLoader
- classloader
- ClassLoader
- ClassLoader
- classloader
- ClassLoader
- classloader
- classloader
- Shell脚本8种字符串截取方法总结
- 曼迪美记项目总结
- Python+OpenCV 程序暂停
- 《Attention-based LSTM for Aspect-level Sentiment Classification》阅读笔记
- Android7.0背光调节
- 聊聊ClassLoader
- QDU 礼尚往来(组合数学之错排公式)
- Nginx简介与安装
- 算法导论优先队列实现
- bzoj1076 [SCOI2008]奖励关
- jsp--学生信息管理系统
- 互联网神经学2016-2017研究报告,七个值得研究的颠覆性创新领域
- [LeetCode]617.Merge Two Binary Trees
- 从神经元到深度学习