类加载器的关系
类加载器的分类
- JVM反对两品种加载器,一种为疏导类加载器(Bootstrap ClassLoader),另外一种是自定义类加载器(User Defined ClassLoader)
- 疏导类加载器是由C/C++编写的无法访问到
- Java虚拟机规定:所有派生于抽象类ClassLoader的类加载器都划分为自定义加载器
- 最常见的类加载器只有三个(如上图所示)
用户自定义的类会被零碎类加载器所加载,外围类库的类会被疏导类加载器所加载
public class ClassLoaderTest { public static void main(String[] args) { //获取零碎类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 //获取其下层:扩大类加载器 ClassLoader extClassLoader = systemClassLoader.getParent(); System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d //获取其下层:获取不到疏导类加载器 ClassLoader bootstrapClassLoader = extClassLoader.getParent(); System.out.println(bootstrapClassLoader);//null //对于用户自定义类来说:默认应用零碎类加载器进行加载 ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 //String类应用疏导类加载器进行加载的。---> Java的外围类库都是应用疏导类加载器进行加载的。 ClassLoader classLoader1 = String.class.getClassLoader(); System.out.println(classLoader1);//null }}
零碎自带的类加载器介绍
启动类加载器(疏导类加载器、Bootstrap ClassLoader)
- 由c/c++语言实现的,嵌套在jvm外部
- 用来加载java外围库
- 并不继承java.lang.ClassLoader,没有父加载器
- 为扩大类加载器和零碎类加载器的父加载器
- 只能加载java、javax、sun结尾的类
扩大类加载器(Extension ClassLoader)
- java语言编写,sun.misc.Launche包下。
- 派生于ClassLoader类,父类加载器为Bootstrap ClassLoader
- 从java.ext.dirs零碎属性指定的目录中加载类库或者加载jre/lib/ext子目录下的类库(用户能够在该目录下编写JAR,也会由此加载器所加载)
零碎类加载器(System ClassLoaderAppClassLoader)
- 派生于ClassLoader,父类加载器为Extension ClassLoader
- 负责加载classpath或者零碎属性java.class.path指定门路下的类库
- java语言编写,sun.misc.Launche包下。
- 负责加载程序中默认的类,能够通过getSystemClassLoader()办法获取该类的加载器。
用户自定义类加载器(前面具体介绍)
- 隔离加载类
- 批改类加载的形式
- 扩大加载源
- 避免源码透露(能够对字节码文件加密)
- 继承ClassLoader类形式实现自定义类加载器
对于ClassLoader
ClassLoader是一个抽象类,其后的所有类加载器都继承此类
注:这些办法都不是形象办法。
获取ClassLoader的门路
public class ClassLoaderTest2 { public static void main(String[] args) { try { //1. ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader(); System.out.println(classLoader); //2. ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader(); System.out.println(classLoader1); //3. ClassLoader classLoader2 = ClassLoader.getSystemClassLoader().getParent(); System.out.println(classLoader2); } catch (ClassNotFoundException e) { e.printStackTrace(); } }}
双亲委派机制(面试)
- Java虚拟机对class文件采纳的是按需加载的形式,当须要应用到这个类的时候才会对它的class文件加载到内存生成class对象,加载的过程中应用的双亲委派模式,即把申请交给父类解决。
- 如果一个类加载器收到了类加载的申请,它不会本人加载,而是先把这个申请给本人的父类加载器去执行
- 如果这个父类加载器还有父类加载器,则会再将申请给本人的父类加载器,顺次递归到顶层的启动类加载器
- 顺次进行判断是否能实现委派(加载此类),若能实现委派则该类就由此加载器加载,若无奈实现委派,则将委托给子类加载器进行判断是否能实现委派,顺次递归到底层加载器,若期间被加载则实现加载阶段不会再递归(注)。
注:类只能被一个加载器所加载。
- 双亲委派的劣势
- 防止类的反复加载
- 爱护程序的平安,避免外围API被篡改
例如:
- 创立一个java.lang.String类,因为有双亲委派的机制,所以会将String类交给疏导类加载器来判断是否能被加载。疏导加载器判断能够加载此类(是外围类中的String),实现加载,则"歹意"写的String类无奈失效,避免String类被歹意篡改。这里也称沙箱平安机制(爱护java外围源代码)
package java.lang;public class String { // static{ System.out.println("我是自定义的String类的动态代码块"); } //谬误: 在类 java.lang.String 中找不到 main 办法 public static void main(String[] args) { System.out.println("hello,String"); }}//因为加载的是外围类的String,在String中找不到main办法
public class StringTest { public static void main(String[] args) { java.lang.String str = new java.lang.String();//无输入 StringTest test = new StringTest(); System.out.println(test.getClass().getClassLoader()); }}
package java.lang;public class ShkStart { //谬误:java.lang.SecurityException: Prohibited package name: java.lang public static void main(String[] args) { System.out.println("hello!"); }}//因为java.lang包由疏导类加载器加载,疏导类中并没有此类,为了平安疏导类
毁坏双亲委派模型:
较大规模的毁坏双亲委派模型的有3种:
- 因为双亲委派模型是在JDK1.2之后才引入的,所以在JDK1.2之前是不合乎双亲委派模型的:
- ClassLoader这类在JDK1.0开始有存在的,在JDK1.2之前,ClassLoader中是通过公有办法loadClassInternal()去调用本人外部的loadClass()。为了满足双亲委派以及向下兼容,在JDK1.2后的ClassLoader类中,又为该类增加了protected的findClass()办法,JDK1.2之后就不举荐通过笼罩重写loadClass()办法了,而是在新增加的findClass()办法中书写本人的类加载逻辑,若loadClass()办法中的父类加载失败则会调用本人的findClass()办法。
因为双亲委派模型的旨意是越外围的类越由高层的加载器所加载(上文提到过的String类),假使这些外围类要去调用用户的根底类,例如JNDI服务(是对 资源进行集中管理和查找,它须要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者(SPI,Service Provider Interface)的代码,但启动类加载器不可能"意识"这些代码)
- 为了解决调用问题,设计了一个线程上下文类加载器(Thread Context ClassLoader),这个类加载器能够通过java.lang.Thread类的setContextClassLoaser()办法进行设置,如果创立线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范畴内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
- 有了这个上下文类加载器,就能够去加载所须要的SPI代码,实际上就是从父类加载器去申请子类加载器去实现类的加载驱动,违反了双亲委派的一般性规定。Java中所有波及SPI的加载动作基本上都采纳这种形式,例如JNDI、JDBC、JCE、JAXB和JBI等。
- 为了满足"热部署"、"动静部署"等性能而导致的。在OSGi(动静模块技术)环境下,类加载器不再是双亲委派模型中的树状构造,而是进一步倒退为更加简单的网状结构,当收到类加载申请时,OSGi将依照程序进行类搜寻
对于类加载器的一些补充
1. JVM中判断一个类是否是同一个类有两个必要条件:
- 这两个类的全限定名要统一
- 这两个类被同一个类加载器加载。
2. 对类加载器的援用:
- JVM必须晓得一个类型是由疏导类加载器(启动类加载器)加载的还是由用户类加载器加载的。
- 如果一个类是由用户类加载器所加载的,那么JVM会将这个类加载器的一个援用作为类信息的一部分保留在办法区。
当解析一个类型到另外一个类型的援用的时候,JVM须要保障这两个类型的类加载器是雷同的。
3. 类的被动应用和被动应用
被动应用:
- 创立类的实例
- 拜访某个类或接口的动态变量,或者对该动态变量赋值
- 调用类的静态方法
- 反射
- 初始化一个类的子类
- JVM启动时被表明为启动类的类
JDK 7 开始提供的动静代理:java.invoke.MethodHandle实例的解析后果,REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化、则初始化
- 除以上7种状况,其余应用Java类的形式都为被动应用,被动应用不会导致类的初始化。