双亲委派机制的优点: 同样的类不会被重复加载。
一、概述:
类加载器:类加载器用于加载 .class字节码文件到内存中(并为每个 .class字节码文件生成 Class对象)。
二、四种类加载器介绍:
编辑
2.1.启动类加载器(Bootstrap ClassLoader): 用于加载jdk的核心类类库中的类,具体加载( jdk/jre/lib
) jdk目录/jre子目录/lib子目录中的类(java官方类库)
、或sun.
boot.class.path属性指定的
路径下的类(了解即可)。 还可以加载扩展类加载器 和 应用程序类加载器。 但通常处于安全性考虑只允许其加载 java、sun、javax包及子包下的类(比如 java.lang.Math, java.lang.String)。
2.2.扩展类加载器 Extension ClassLoader:
用于加载: Java 语言编写,由 sun.misc.Launcher$ExtClassLoader实现。其继承了 java.lang.ClassLoader抽象类。 父加载器为启动类加载器(Bootstrap ClassLoader)。
用于加载Java的扩展类库, jdk
/jre/lib/ext目录中的 JAR 压缩文件和类。
2.3.应用程序类加载器 ApplicationClassLoader:
Java 语言编写,由 sun.misc.Launcher$AppClassLoader 实现。其继承了 java.lang.ClassLoader抽象类。 父加载器为扩展类加载器。用于加载:自己编写的类、第三方库提供的类、一些其他的资源文件。是java开发的应用程序默认的类加载器,一般来说,Java 开发的应用程序中的类都是由它来完成加载。
注意:应用程序类加载器 ApplicationClassLoader也叫做系统类加载器, 因为是项目默认的类加载器。
2.4.自定义类加载器:
日常开发中,上述3种类加载器相互配合就几乎可以加载所有的类及相关资源。 但必要时,可以自定义类加载器,来定制类的加载方式。
注意: 如果一个项目中用户没有创建并启动自定义类加载器,则本项目中就不涉及用户自定义类加载器
public class CustomClassLoader extends ClassLoader { //省略类自定义加载器的其他具体实现代码。 public static void main(String[] args) { try { // 创建自定义的类加载器1 CustomClassLoader loader1 = new CustomClassLoader(); Class<?> clazz1 = loader1.findClass("com.test.java.User"); // 创建自定义的类加载器2 CustomClassLoader loader2 = new CustomClassLoader(); Class<?> clazz2 = loader2.findClass("com.test.java.User"); // clazz1和clazz2对应了不同的类模版结构 System.out.println(clazz1 == clazz2); // false System.out.println(clazz1.getClassLoader()); // com.test.java.CustomClassLoader@4aa298b7 System.out.println(clazz2.getClassLoader()); // com.test.java.CustomClassLoader@28d93b30 } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } }
由于父加载器的类型对于子加载器是可见的,所以父加载器中加载过的类型,就不会在子加载器中重复加载。但是注意,类加载器“邻居”间,同一类型仍然可以被加载多次,因为互相并不可见。(如上面的 loader1 和 loader2可以把User类加载两次)
自定义类加载器的优点:
1.修改类加载的方式: 可以根据实际情况在某个时间点按需进行动态加载(延迟加载)。
2.扩展加载源:比如从数据库、网络、甚至是电视机机顶盒进行加载。
3.防止源码泄漏:Java代码容易被编译和篡改,可以进行编译加密。那么可以用自定义类加载器,解密字节码文件。
四种类加载器总结:
2.4.1.父子关系:
- 1.启动类加载器没有父加载器, 子加载器是扩展类加载器。
2.
扩展类加载器的子加载器是应用程序类加载器。- 3.应用程序类加载器的子加载器是用户自定义类加载器
- 4.用户自定义类加载器没有子加载器。
2.4.2.加载路径:
- 启动类加载器,加载: java运行环境中的类 (jre中的类): jdk\jre\lib中的类及资源
- 扩展类加载器,加载: java运行环境中的扩展路径下的类 (jre中ext子路径下的类及资源): jdk\jre\lib\ext 子路径中的类及资源 (比启动类加载器加载的范围更小)
- 应用程序类加载器,加载: 自己开发的项目下的类及资源。
- 自定义类加载器,加载: 软件工程师指定的路径下的类及资源。
三、双亲委派机制下的类加载流程:
编辑
上图中列出了三种类加载器加载类的过程(1.应用程序类加载器AppClassLoader, 2.扩展类加载器ExtClassLoader、 3. 引导类加载器Bootstrap ClassLoader) (没有自定义类加载器: 如果一个项目中用户没有创建并启动自定义类加载器,则本项目中就不涉及用户自定义类加载器)
三种类加载器依据双亲委派机制加载类的具体过程如下:
假设要加载一个自己编写的 day1114.Cat类,则具体过程分为如下两大块(假定没有自定义类加载器):
1.查询day1114.Cat类是否加载过(如果加载过就直接使用,没加载过就将加载的需求委派给父加载器加载)
- 应用程序类加载器 ApplicationClassLoader查询是否加载过day1114.Cat类如果加载过就直接使用,没加载过就将加载的需求委派给父加载器加载(扩展类加载器Extension ClassLoader)
- 扩展类加载器Extension ClassLoader查询是否加载过day1114.Cat类如果加载过就直接使用,没加载过就将加载的需求委派给父加载器加载(启动类加载器Bootstrap ClassLoader)
- 启动类加载器Bootstrap ClassLoader查询是否加载过day1114.Cat类如果加载过就直接使用,没加载过就尝试加载day1114.Cat类。能加载就加载并使用,如果无法加载就将加载请求委派子加载类加载。
2.加载day1114.Cat类:
- 如果启动类加载器没加载过就尝试加载day1114.Cat类。能加载就加载并使用并结束加载Cat类的过程(子类加载器就不会再加载这个Cat类了),如果无法加载就将加载请求委派子加载类加载(扩展类加载器Extension ClassLoader)
- 扩展类加载器Extension ClassLoader尝试加载day1114.Cat类。能加载就加载并使用并结束加载Cat类的过程(子类加载器就不会再加载这个Cat类了),如果无法加载就将加载请求委派子加载类加载(应用程序类加载器Application ClassLoader)
- 应用程序类加载器Application ClassLoader尝试加载day1114.Cat类。能加载就加载并使用并结束加载Cat类的过程(子类加载器就不会再加载这个Cat类了),如果无法加载就将加载请求委派子加载类加载(自定义类加载器User ClassLoader), 但如果项目中没有创建自定义类加载器 则会抛出ClassNotFoundException的异常。
优点: 同样的类不会被重复加载(避免类的重复加载,确保一个类的全局唯一性,保护程序安全,防止核心 API 被随意篡改)。
由于父加载器的类型对于子加载器是可见的,所以父加载器中加载过的类型,就不会在子加载器中重复加载。但是注意,类加载器“邻居”间,同一类型仍然可以被加载多次,因为互相并不可见。(如下面的 loader1 和 loader2)
举例说明:
假设自己定义了一个 java.lang.String类 (命名空间<包名和类名>和java官方编写的java.lang.String一样,类中的代码不一样)。假设没有双亲委派机制, 则类的加载过程:
1.java.lang.String类由应用程序类加载器(Application ClassLoader)加载
注意:在java内存中,同一命名空间中,不会出现全限定名相同的两个类
2. 启动类加载器则 不会再加载 /jre/lib/rt.jar 下的 java.lang.String
则核心类 java.lang.String 被篡改成了自定义的String类。
四、案例测试:
用代码查看三种类加载器
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@232204a1 // 引导类加载器 ClassLoader bootstrapClassLoader = extClassLoader.getParent(); System.out.println(bootstrapClassLoader); // null (因为是C++编写的所以为null) }
4.1.引导类加载器:
public static void main(String[] args) { //获取引导类加载器加载的类库 URL[] urLs = Launcher.getBootstrapClassPath().getURLs(); for (URL urL : urLs) { System.out.println(urL); } // 从上边的路径中随意选择一个类,查看其类加载器 ClassLoader classLoader1 = Object.class.getClassLoader(); System.out.println(classLoader1); // null } //运行结果: 有两种,一个是jdk1.8.0_77/jre/lib下的jar, 一种是null //null并不是说明没有引导类加载器,而是因为引导类加载器是用C/C++语言实现的 //file:/C:/Program Files/Java/jdk1.8.0_77/jre/lib/ 下的很多jar文件 //null
4.2.扩展类加载器:
可以看到扩展类加载器加载的是 jdk\jre\lib\ext中的资源 (另一个加载路径 windows\sun\java\lib\ext了解即可)
public static void main(String[] args) { // 扩展类加载器加载的类库 String extDirs = System.getProperty("java.ext.dirs"); for (String extPath : extDirs.split(";")) { System.out.println(extPath); } //C:\Program Files\Java\jdk1.8.0_77\jre\lib\ext //C:\Windows\Sun\Java\lib\ext // 从上边的路径中随意选择一个类,查看其类加载器 ClassLoader classLoader2 = CurveDB.class.getClassLoader(); System.out.println(classLoader2); //sun.misc.Launcher$ExtClassLoader@4b67cf4d }
4.3.应用程序类加载器:
查看系统默认的应用程序类加载器,和自己定义的类采用的类加载器 (都是ApplicationClassLoader)
public static void main(String[] args) { // 获取应用程序类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 // 对于自定义的类,查看其类加载器 ClassLoader classLoader3 = Cat.class.getClassLoader(); System.out.println(classLoader3); //sun.misc.Launcher$AppClassLoader@18b4aac2 }
参考文章:
Java类加载器-CSDN博客https://blog.csdn.net/scj1022/article/details/135054087
author : zhaoyq 2024-11-14
标签:java,自定义,ClassLoader,应用程序,Cat,双亲,加载 From: https://www.cnblogs.com/zhaoyongqi/p/18546462