类加载器的关系
类加载器的分类
- 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 类的形式都为被动应用,被动应用不会导致类的初始化。
-