优化的定义,宁波seo搜索引擎优化,seo怎么收费,网站建设需要的技能有哪些转载自 【JVM】浅谈双亲委派和破坏双亲委派
一、前言
笔者曾经阅读过周志明的《深入理解Java虚拟机》这本书#xff0c;阅读完后自以为对jvm有了一定的了解#xff0c;然而当真正碰到问题的时候#xff0c;才发现自己读的有多粗糙#xff0c;也体会到只有实践才能加深理…转载自 【JVM】浅谈双亲委派和破坏双亲委派
一、前言
笔者曾经阅读过周志明的《深入理解Java虚拟机》这本书阅读完后自以为对jvm有了一定的了解然而当真正碰到问题的时候才发现自己读的有多粗糙也体会到只有实践才能加深理解正应对了那句话——“Talk is cheap, show me the code”。前段时间笔者同事提出了一个关于类加载器破坏双亲委派的问题以我们常见到的数据库驱动Driver为例为什么要实现破坏双亲委派下面一起来重温一下。 二、双亲委派
想要知道为什么要破坏双亲委派就要先从什么是双亲委派说起在此之前我们先要了解一些概念
对于任意一个类都需要由加载它的类加载器和这个类本身来一同确立其在Java虚拟机中的唯一性。
什么意思呢我们知道判断一个类是否相同通常用equals()方法isInstance()方法和isAssignableFrom()方法。来判断对于同一个类如果没有采用相同的类加载器来加载在调用的时候会产生意想不到的结果
public class DifferentClassLoaderTest {public static void main(String[] args) throws Exception {ClassLoader classLoader new ClassLoader() {Overridepublic Class? loadClass(String name) throws ClassNotFoundException {String fileName name.substring(name.lastIndexOf(.) 1) .class;InputStream stream getClass().getResourceAsStream(fileName);if (stream null) {return super.loadClass(name);}try {byte[] b new byte[stream.available()];// 将流写入字节数组b中stream.read(b);return defineClass(name, b, 0, b.length);} catch (IOException e) {e.printStackTrace();}return super.loadClass(name);}};Object obj classLoader.loadClass(jvm.DifferentClassLoaderTest).newInstance();System.out.println(obj.getClass());System.out.println(obj instanceof DifferentClassLoaderTest);}
}
输出结果
class jvm.DifferentClassLoaderTest
false
如果在通过classLoader实例化的使用直接转化成DifferentClassLoaderTest对象
DifferentClassLoaderTest obj (DifferentClassLoaderTest) classLoader.loadClass(jvm.DifferentClassLoaderTest).newInstance();
就会直接报java.lang.ClassCastException:因为两者不属于同一类加载器加载所以不能转化 2.1、为什么需要双亲委派
基于上述的问题如果不是同一个类加载器加载即时是相同的class文件也会出现判断不想同的情况从而引发一些意想不到的情况为了保证相同的class文件在使用的时候是相同的对象jvm设计的时候采用了双亲委派的方式来加载类。 双亲委派如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。 这里有几个流程要注意一下
子类先委托父类加载父类加载器有自己的加载范围范围内没有找到则不加载并返回给子类子类在收到父类无法加载的时候才会自己去加载
jvm提供了三种系统加载器
启动类加载器Bootstrap ClassLoaderC实现在java里无法获取负责加载/lib下的类。扩展类加载器Extension ClassLoader Java实现可以在java里获取负责加载/lib/ext下的类。系统类加载器/应用程序类加载器Application ClassLoader是与我们接触对多的类加载器我们写的代码默认就是由它来加载ClassLoader.getSystemClassLoader返回的就是它。
附上三者的关系 2.2、双亲委派的实现
双亲委派的实现其实并不复杂其实就是一个递归我们一起来看一下ClassLoader里的代码
protected Class? loadClass(String name, boolean resolve)throws ClassNotFoundException{// 同步上锁synchronized (getClassLoadingLock(name)) {// 先查看这个类是不是已经加载过Class? c findLoadedClass(name);if (c null) {long t0 System.nanoTime();try {// 递归双亲委派的实现先获取父类加载器不为空则交给父类加载器if (parent ! null) {c parent.loadClass(name, false);// 前面提到bootstrap classloader的类加载器为null通过find方法来获得} else {c findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {}if (c null) {// 如果还是没有获得该类调用findClass找到类long t1 System.nanoTime();c findClass(name);// jvm统计sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}// 连接类if (resolve) {resolveClass(c);}return c;}} 三、破坏双亲委派
3.1、为什么需要破坏双亲委派
因为在某些情况下父类加载器需要委托子类加载器去加载class文件。受到加载范围的限制父类加载器无法加载到需要的文件以Driver接口为例由于Driver接口定义在jdk当中的而其实现由各个数据库的服务商来提供比如mysql的就写了MySQL Connector那么问题就来了DriverManager也由jdk提供要加载各个实现了Driver接口的实现类然后进行管理但是DriverManager由启动类加载器加载只能记载JAVA_HOME的lib下文件而其实现是由服务商提供的由系统类加载器加载这个时候就需要启动类加载器来委托子类来加载Driver实现从而破坏了双亲委派这里仅仅是举了破坏双亲委派的其中一个情况。
3.2、破坏双亲委派的实现
我们结合Driver来看一下在spiService Provider Inteface中如何实现破坏双亲委派。
先从DriverManager开始看平时我们通过DriverManager来获取数据库的Connection
String url jdbc:mysql://localhost:3306/testdb;
Connection conn java.sql.DriverManager.getConnection(url, root, root);
在调用DriverManager的时候会先初始化类调用其中的静态块
static {loadInitialDrivers();println(JDBC DriverManager initialized);
}private static void loadInitialDrivers() {...// 加载Driver的实现类AccessController.doPrivileged(new PrivilegedActionVoid() {public Void run() {ServiceLoaderDriver loadedDrivers ServiceLoader.load(Driver.class);IteratorDriver driversIterator loadedDrivers.iterator();try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {}return null;}});...
}
为了节约空间笔者省略了一部分的代码重点来看一下ServiceLoader.load(Driver.class)
public static S ServiceLoaderS load(ClassS service) {// 获取当前线程中的上下文类加载器ClassLoader cl Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);
}
可以看到load方法调用获取了当前线程中的上下文类加载器那么上下文类加载器放的是什么加载器呢
public Launcher() {...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);...
}
在sun.misc.Launcher中我们找到了答案在Launcher初始化的时候会获取AppClassLoader然后将其设置为上下文类加载器而这个AppClassLoader就是之前上文提到的系统类加载器Application ClassLoader所以上下文类加载器默认情况下就是系统加载器。
继续来看下ServiceLoader.load(service, cl)
public static S ServiceLoaderS load(ClassS service,ClassLoader loader){return new ServiceLoader(service, loader);
}private ServiceLoader(ClassS svc, ClassLoader cl) {service Objects.requireNonNull(svc, Service interface cannot be null);// ClassLoader.getSystemClassLoader()返回的也是系统类加载器loader (cl null) ? ClassLoader.getSystemClassLoader() : cl;acc (System.getSecurityManager() ! null) ? AccessController.getContext() : null;reload();
}public void reload() {providers.clear();lookupIterator new LazyIterator(service, loader);
}
上面这段就不解释了比较简单然后就是看LazyIterator迭代器
private class LazyIterator implements IteratorS{// ServiceLoader的iterator()方法最后调用的是这个迭代器里的nextpublic S next() {if (acc null) {return nextService();} else {PrivilegedActionS action new PrivilegedActionS() {public S run() { return nextService(); }};return AccessController.doPrivileged(action, acc);}}private S nextService() {if (!hasNextService())throw new NoSuchElementException();String cn nextName;nextName null;Class? c null;// 根据名字来加载类try {c Class.forName(cn, false, loader);} catch (ClassNotFoundException x) {fail(service,Provider cn not found);}if (!service.isAssignableFrom(c)) {fail(service,Provider cn not a subtype);}try {S p service.cast(c.newInstance());providers.put(cn, p);return p;} catch (Throwable x) {fail(service,Provider cn could not be instantiated,x);}throw new Error(); // This cannot happen}public boolean hasNext() {if (acc null) {return hasNextService();} else {PrivilegedActionBoolean action new PrivilegedActionBoolean() {public Boolean run() { return hasNextService(); }};return AccessController.doPrivileged(action, acc);}}private boolean hasNextService() {if (nextName ! null) {return true;}if (configs null) {try {// 在classpath下查找META-INF/services/java.sql.Driver名字的文件夹// private static final String PREFIX META-INF/services/;String fullName PREFIX service.getName();if (loader null)configs ClassLoader.getSystemResources(fullName);elseconfigs loader.getResources(fullName);} catch (IOException x) {fail(service, Error locating configuration files, x);}}while ((pending null) || !pending.hasNext()) {if (!configs.hasMoreElements()) {return false;}pending parse(service, configs.nextElement());}nextName pending.next();return true;}}
好了这里基本就差不多完成整个流程了一起走一遍 四、总结
Driver剩余的加载过程就省略了有兴趣的园友可以继续深入了解一下不得不说jvm博大精深看起来容易真正到了用起来才发现各种问题也只有实践才能加深理解最后谢谢各位园友观看如果有描述不对的地方欢迎指正与大家共同进步 参考部分
https://blog.csdn.net/yangcheng33/article/details/52631940周志明-《深入理解JAVA虚拟机JVM高级特性与最佳实践》