jvm 双亲委派,类加载器 双亲委派
00-1010类加载器1、启动类加载器2、扩展类加载器3、应用类加载器4、家长委托模式5、自定义类加载器5.1、使用场景5.2、步骤总结
目录
Java虚拟机设计团队打算把“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放在Java虚拟机之外的类加载阶段来实现,这样方便让应用程序自己决定如何去获取所需的类.实现这个动作的代码叫做"类加载器"。对于任何一个类,必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,的每个类加载器,都有一个独立的类命名空间。这句话可以表达得更通俗一点:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使两个类来自同一个类文件,由同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等.
加载的类描述引导类加载器JAVA_HOME/jre/lib的名称不能直接访问扩展类加载器JAVA_HOME/jre/lib/ext。上级是Bootstrap。显示nullApplication ClassLoader(应用程序类加载器)类路径。上层是扩展定制类加载器。自定义的上层是应用程序。
00-1010可以通过在控制台中输入指令启动类加法器来加载类。是用C写的,看不到源代码;其他的类加载器都是用Java写的,也就是一些Java类,比如扩展类加载器和应用类加载器。
//查询所有类URL[]URLs=sun . misc . launcher . getbootstrapclasspath()。getURLs();for(URL URL : URLs){ system . out . println(URL);}//查询结果文件:/c :/Program Files/Java/JRE 1 . 8 . 0 _ 131/lib/resources . jarfile 3360/c 3360/Program Files/Java/JRE 1 . 8 . 0 _ 131/lib/rt . jarfile 3331 c :/Program Files/Java/JRE 1 . 8 . 0 _ 131/lib/sunrsa sign . jarfile 3360/Program Files/Java/c 3:jarfile :/c :/Program Files/Java/jre 1 . 8 . 0 _ 131/Classes从上面可以看出,启动类是用JRE和jre/lib目录下的核心库加载的,具体路径要看你的jre安装在哪里.
00-1010如果classpath和JAVA_HOME/jre/lib/ext下有同名的类,将使用扩展类加载器进行加载。当应用程序类装入器发现扩展类装入器已经装入了同名的类时,就不会再装入了。
URL[]URLs=((URL class loader)class loader . getsystemclassloader()。getParent())。getURLs();for(URL URL : URLs){ system . out . println(URL);} file :/c :/Program Files/Java/JRE 1 . 8 . 0 _ 131/lib/ext/access-bridge-64 . jarfile :/c :/Program Files/Java/JRE 1 . 8 . 0 _ 131/lib/ext/cldrdata . jarfile :/c :/Program Files/Java/JRE 1 . 8 . 0 _ 131/lib/ext/dnsns . jarfile 33366
.0_131/lib/ext/dns_sd.jarfile:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/jaccess.jarfile:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/jfxrt.jarfile:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/localedata.jarfile:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/nashorn.jarfile:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/sunec.jarfile:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/sunjce_provider.jarfile:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/sunmscapi.jarfile:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/sunpkcs11.jarfile:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/zipfs.jar这些类库具体是什么不重要,只需要知道不同的类库可能是被不同的类加载器加载的。
3、应用程序类加载器
URL[] urls = ((URLClassLoader) ClassLoader.getSystemClassLoader()).getURLs();for (URL url : urls) { System.out.println(url);}
file:/{项目工程目录}/bin/这是当前java工程的bin目录,也就是我们自己的Java代码编译成的class文件所在。
4、双亲委派模式
双亲委派模式,调用类加载器ClassLoader 的 loadClass 方法时,查找类的规则。loadClass源码
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ synchronized (getClassLoadingLock(name)) { // 首先查找该类是否已经被该类加载器加载过了 Class<?> c = findLoadedClass(name); //如果没有被加载过 if (c == null) { long t0 = System.nanoTime(); try { //看是否被它的上级加载器加载过了 Extension的上级是Bootstarp,但它显示为null if (parent != null) { c = parent.loadClass(name, false); } else { //看是否被启动类加载器加载过 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader //捕获异常,但不做任何处理 } if (c == null) { //如果还是没有找到,先让拓展类加载器调用findClass方法去找到该类,如果还是没找到,就抛出异常 //然后让应用类加载器去找classpath下找该类 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; }}有一个描述类加载器加载类过程的术语:双亲委派模型。然而这是一个很有误导性的术语,它应该叫做单亲委派模型(Parent-Delegation Model)。但是没有办法,大家都已经这样叫了。所谓双亲委派,这个亲就是指
ClassLoader
里的全局变量parent
,也就是父加载器。双亲委派的具体过程如下:
当一个类加载器接收到类加载任务时,先查缓存里有没有,如果没有,将任务委托给它的父加载器去执行。父加载器也做同样的事情,一层一层往上委托,直到最顶层的启动类加载器为止。如果启动类加载器没有找到所需加载的类,便将此加载任务退回给下一级类加载器去执行,而下一级的类加载器也做同样的事情。如果最底层类加载器仍然没有找到所需要的class文件,则抛出异常。所以是一条线传上再传下,并没有什么双亲。
为什么要双亲委派?
答:确保类的全局唯一性。
如果你自己写的一个类与核心类库中的类重名,会发现这个类可以被正常编译,但永远无法被加载运行。因为你写的这个类不会被应用类加载器加载,而是被委托到顶层,被启动类加载器在核心类库中找到了。如果没有双亲委托机制来确保类的全局唯一性,谁都可以编写一个java.lang.Object类放在classpath下,那应用程序就乱套了。
从安全的角度讲,通过双亲委托机制,Java虚拟机总是先从最可信的Java核心API查找类型,可以防止不可信的类假扮被信任的类对系统造成危害。
5、自定义类加载器
如果我们自己去实现一个类加载器,基本上就是继承ClassLoader之后重写findClass方法,且在此方法的最后调包defineClass。
5.1、使用场景
想加载非 classpath 随意路径中的类文件通过接口来使用实现,希望解耦时,常用在框架设计这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常见于 tomcat 容器
5.2、步骤
继承ClassLoader父类要遵从双亲委派机制,重写 findClass 方法不是重写loadClass方法,否则不会走双亲委派机制读取类文件的字节码调用父类的 defineClass 方法来加载类使用者调用该类加载器的 loadClass 方法protected Class<?> findClass(final String name) throws ClassNotFoundException { // 1、安全检查 // 2、根据绝对路径把硬盘上class文件读入内存 byte[] raw = getBytes(name); // 3、将二进制数据转换成class对象 return defineClass(raw);}
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注盛行IT的更多内容!郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。