浅谈双亲委派和破坏双亲委派

浅谈双亲委派和破坏双亲委派(https://cloud.tencent.com/developer/article/1176641

双亲委派

对于任意一个类,都需要由加载它的类加载器和这个类本身来一同确立其在Java虚拟机中的唯一性

什么意思呢?我们知道,判断一个类是否相同,通常用equals()方法,isInstance()方法和isAssignableFrom()方法。来判断,对于同一个类,如果没有采用相同的类加载器来加载,在调用的时候,会产生意想不到的结果:

  1. public class DifferentClassLoaderTest {
  2. public static void main(String[] args) throws Exception {
  3. ClassLoader classLoader = new ClassLoader() {
  4. @Override
  5. public Class<?> loadClass(String name) throws ClassNotFoundException {
  6. String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
  7. InputStream stream = getClass().getResourceAsStream(fileName);
  8. if (stream == null) {
  9. return super.loadClass(name);
  10. }
  11. try {
  12. byte[] b = new byte[stream.available()];
  13. // 将流写入字节数组b中
  14. stream.read(b);
  15. return defineClass(name, b, 0, b.length);
  16. } catch (IOException e) {
  17. e.printStackTrace();
  18. }
  19. return super.loadClass(name);
  20. }
  21. };
  22. Object obj = classLoader.loadClass("jvm.DifferentClassLoaderTest").newInstance();
  23. System.out.println(obj.getClass());
  24. System.out.println(obj instanceof DifferentClassLoaderTest);
  25. }
  26. }
  27. // 输出结果:
  28. class jvm.DifferentClassLoaderTest
  29. false
  30. // 如果在通过classLoader实例化的使用,直接转化成DifferentClassLoaderTest对象:
  31. DifferentClassLoaderTest obj = (DifferentClassLoaderTest) classLoader.loadClass("jvm.DifferentClassLoaderTest").newInstance();
  32. // 就会直接报java.lang.ClassCastException:,因为两者不属于同一类加载器加载,所以不能转化!

为什么需要双亲委派

基于上述的问题:如果不是同一个类加载器加载,即时是相同的class文件,也会出现判断不相同的情况,从而引发一些意想不到的情况,为了保证相同的class文件,在使用的时候,是相同的对象,jvm设计的时候,采用了双亲委派的方式来加载类。

双亲委派:如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。

这里有几个流程要注意一下:

  1. 子类先委托父类加载
  2. 父类加载器有自己的加载范围,范围内没有找到,则不加载,并返回给子类
  3. 子类在收到父类无法加载的时候,才会自己去加载

jvm提供了三种系统加载器:

  1. 启动类加载器(Bootstrap ClassLoader):C++实现,在java里无法获取,负责加载/lib下的类。
  2. 扩展类加载器(Extension ClassLoader): Java实现,可以在java里获取,负责加载/lib/ext下的类。
  3. 系统类加载器/应用程序类加载器(Application ClassLoader):是与我们接触最多的类加载器,我们写的代码默认就是由它来加载,ClassLoader.getSystemClassLoader返回的就是它。

双亲委派.png

双亲委派的实现

  1. protected Class<?> loadClass(String name, boolean resolve)
  2. throws ClassNotFoundException
  3. {
  4. // 同步上锁
  5. synchronized (getClassLoadingLock(name)) {
  6. // 先查看这个类是不是已经加载过
  7. Class<?> c = findLoadedClass(name);
  8. if (c == null) {
  9. long t0 = System.nanoTime();
  10. try {
  11. // 递归,双亲委派的实现,先获取父类加载器,不为空则交给父类加载器
  12. if (parent != null) {
  13. c = parent.loadClass(name, false);
  14. // 前面提到,bootstrap classloader的类加载器为null,通过find方法来获得
  15. } else {
  16. c = findBootstrapClassOrNull(name);
  17. }
  18. } catch (ClassNotFoundException e) {
  19. }
  20. if (c == null) {
  21. // 如果还是没有获得该类,调用findClass找到类
  22. long t1 = System.nanoTime();
  23. c = findClass(name);
  24. // jvm统计
  25. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
  26. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
  27. sun.misc.PerfCounter.getFindClasses().increment();
  28. }
  29. }
  30. // 连接类
  31. if (resolve) {
  32. resolveClass(c);
  33. }
  34. return c;
  35. }
  36. }

破坏双亲委派

为什么需要破坏双亲委派?示例说明

因为在某些情况下父类加载器需要委托子类加载器去加载class文件。受到加载范围的限制,父类加载器无法加载到需要的文件,以Driver接口为例,由于Driver接口定义在jdk当中的,而其实现由各个数据库的服务商来提供,比如mysql的就写了MySQL Connector,那么问题就来了,DriverManager(也由jdk提供)要加载各个实现了Driver接口的实现类,然后进行管理,但是DriverManager由启动类加载器加载,只能记载JAVA_HOME的lib下文件,而其实现是由服务商提供的,由系统类加载器加载,这个时候就需要启动类加载器来委托子类来加载Driver实现,从而破坏了双亲委派,这里仅仅是举了破坏双亲委派的其中一个情况。

破坏双亲委派的实现

我们结合Driver来看一下在spi(Service Provider Inteface)中如何实现破坏双亲委派。

  1. // 先从DriverManager开始看,平时我们通过DriverManager来获取数据库的Connection:
  2. String url = "jdbc:mysql://localhost:3306/testdb";
  3. Connection conn = java.sql.DriverManager.getConnection(url, "root", "root");
  4. // 在调用DriverManager的时候,会先初始化类,调用其中的静态块:
  5. static {
  6. loadInitialDrivers();
  7. println("JDBC DriverManager initialized");
  8. }
  9. private static void loadInitialDrivers() {
  10. ...
  11. // 加载Driver的实现类
  12. AccessController.doPrivileged(new PrivilegedAction<Void>() {
  13. public Void run() {
  14. ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
  15. Iterator<Driver> driversIterator = loadedDrivers.iterator();
  16. try{
  17. while(driversIterator.hasNext()) {
  18. driversIterator.next();
  19. }
  20. } catch(Throwable t) {
  21. }
  22. return null;
  23. }
  24. });
  25. ...
  26. }

打破双亲委派.png