类加载器
类加载器是 JVM 执行类加载机制的前提。
ClassLoader作用:
ClassLoader是Java的核心组件,所有Class都是有ClassLoader进行加载的,ClassLoader负责通过各种方式将Class信息的二进制数据流读入JVM内部,转换一个与目标类对应的java.lang.Class对象实例。然后交给Java虚拟
机进行链接,初始化等操作。因此,ClassLoader在整个装载阶段,只能影响到类加载,而无法通过ClassLoader去改变类的链接和初始化行为。

a,大厂面试题:
蚂蚁金服:
深入分析ClassLoader,双亲委派机制?
类加载的双亲委派模型是什么?
一面:双亲委派机制及使用原因
百度:
都有哪些类加载器,这些类加载器都加载哪些文件?
手写一个类加载Demo
Class的forName("java.lang.String")和 Class的getClassLoader()的loadClass("java.lang.String")有什么区别?
腾讯:
什么是双亲委派模型?
类加载有哪些?
小米:
双亲委派模型介绍一下
b,类的加载分类:显示加载 vs 隐式加载
class文件显示加载与隐式加载的方式是指JVM加载class文件到内存的方式。
显示加载指的是在代码中调用ClassLoader加载class对象,如果直接使用Class.forName(name)或
this.getClass().getClassLoader().loadClass()加载class对象。
隐式加载则是不直接在代码中调用ClassLoader的方法加载class对象,而是通过虚拟机自动加载到内存中,如在加载某个类的class文件时,该class文件中引用了另外一个类的对象,此时额外引用的类将通过JVM自动加载到内存。(一般混合使用)
/**
* @Author ylc
* @Date 2022/4/8 9:34
* @Version 1.0
*
* 判别:显示加载 和 隐式加载
*/
public class UserTest {
public static void main(String[] args) {
User user = new User(); //隐式加载
try {
Class.forName("com.ylc.java12.User");//显示加载
ClassLoader.getSystemClassLoader().loadClass("com.ylc.java12.User");//显示加载
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
c,类加载器的必要性
避免在开发中遇到java.lang.ClassNotFoundException异常或java.lang.NoClassDefFoundError异常时,能快速锁定位置。
需要支持类的动态加载或需要对编译后的字节码文件进行加解密操作时,需要与类加载器打交道。
d,命名空间
1,何为类的唯一性?
对于任意一个类,都需要有加载它的类加载器和这个类本身一同确认Java虚拟机中的唯一性。每一个类加载器,都有个都拥有一个独立的类名称空间:比较两个类是否相等,只有在这两个类是有同一类加载器加载的前提下才有意义。
2,命名空间
每一个类加载器都有自己的命名空间,命名空间由该加载器即所有的父类加载器所加载的类组成。
在同一命名空间中,不会出现类的完整名字相同的两个类
在不同的命名空间中,有可能会出现类的完整名字相同的两个类
e,类加载机制的基本特征
1,双亲委派模型
2,可见性
3,单一性
JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(Use-Defined ClassLoader)。
自定义类加载器一般指程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有定义。而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义加载类。

除了顶层的启动类加载器外,其余的类加载器都应当有自己的“父类”加载器。
不同类加载器看似是继承关系,实际上是包含关系。在下层加载器中,包含上层加载器的引用。
a,引导类加载器
- 启动类加载器(引导类加载器,Bootstrp ClassLoader)
- 这个类加载器使用C/C++语言实现的,嵌套在JVM内部
- 她用来加载Java的核心类。用于提供JVM自身需要的类。
- 并不继承自java.lang.ClassLoader,没有父加载器。
- 出于安全考虑,Bootstrap启动类加载器只加载包名为java,javax,sun等开头的类
- 加载扩展类和应用程序加载类,并指向他们的父类加载器。
启动类加载器使用C++编写的?Yes
C/C++:指针函数&函数指针,C++支持多继承,更加高效
Java:由C++演变而来,(C++),单继承
b,扩展类加载器(Extension ClassLoader)
Java语言编写,由sun.misc.Launcher$ExtClassLoader实现
继承于ClassLoader类
父类加载类为启动类加载器
从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库。如果用户创建的JAR放在此目录下,也会自动有扩展类加载器加载。

c,系统类加载器
- java语言编写,由sun.misc.Launcher$ClassLoader实现
- 继承于ClassLoader类
- 父类加载器为扩展类加载器
- 他负责加载环境变量classpath或系统属性 java.class.path 指定路径下的类库
- 应用程序中的类加载器默认是系统类加载器。
- 它是用户自定义类加载器的默认父加载器
- 通过ClassLoader的getSystemClassLoader()方法可以获取到该类加载器
d,用户自定义类加载器
通过类加载器可以实现非常绝妙的插件机制,如著名的OSGI组件框架,在如Eclipse的插件机制。类加载器为应用程序提供动态增加新功能,无需重新打包发布应用程序。
自定义加载器能够实现应用隔离,如Tomcat,Spring等中间件和组件框架都在内部实现自定义的加载器,并通过自定义加载器隔离不同的组件模块。
自定义类加载通常需要继承于ClassLoader。
测试不同的类加载器
每个Class对象都会包含一个定义它的ClassLoader的一个引用。
获取ClassLoader的途径
1, 获取当前类的ClassLoader
clazz.getClassLoader()
2,获得当前线程上下文的ClassLoader
Thread.currentThread().getContextClassLoader()
3,获得系统的ClassLoader
ClassLoader.getSystemClassLoader()
说明:
引导类加载器与另外两种类加载并不是同一层的类加载器,引导类加载器是使用C++语言编写,而另外两种类加载器则是使用Java语言编写而成。由引导类加载器压根就不是Java类,因此Java程序中只能打印出null。
数组类的Class对象,不是由类加载去创建的,而是在Java运行期JVM根据需要自动创建。与数组中元素类型的类加载器一样,如果数组是基本类型就没有类加载器。
package com.ylc.java12; /** * @Author ylc * @Date 2022/4/8 23:14 * @Version 1.0 * * null 表示引导类加载器这是C/C++的 */ public class ClassLoaderTest1 { public static void main(String[] args) { //获取系统该类加载器 ClassLoader systemClass = ClassLoader.getSystemClassLoader(); System.out.println(systemClass);//sun.misc.Launcher$AppClassLoader@18b4aac2 //获取扩展类加载器 ClassLoader extClassLoader = systemClass.getParent(); System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@511d50c0 //试图获取引导类加载器:失败 ClassLoader bootstrapClassLoader = extClassLoader.getParent(); System.out.println(bootstrapClassLoader);//null //############### try { ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader(); System.out.println(classLoader);//null //自定义的类默认使用系统类加载器 ClassLoader classLoader1 = Class.forName("com.ylc.java12.ClassLoaderTest1").getClassLoader(); System.out.println(classLoader1);//sun.misc.Launcher$AppClassLoader@18b4aac2 //关于数组类型的加载:使用的类的加载器与数组元素的类的加载器相同 String[] arrStr = new String[10]; System.out.println(arrStr.getClass().getClassLoader());//null,表示使用的引导类加载器 ClassLoaderTest1[] arr1 = new ClassLoaderTest1[10]; System.out.println(arr1.getClass().getClassLoader());//sun.misc.Launcher$AppClassLoader@18b4aac2 int[] arr2 = new int[10]; System.out.println(arr2.getClass().getClassLoader());//null } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
ClassLoader源码解析
ClassLoader与现在类加载器的关系:

除了以上虚拟机自带的加载器外,用户还可以定制自己的类加载器。Java提供抽象类 java.lang.ClassLoader,所有用户自定义的类加载都应该继承ClassLoader类。
A: ClassLoader的主要方法
getParent() 该返回类加载器的超类加载器
loadClass(String name) throws ClassNotFoundException
加载名称为 name 的类,返回结果java.lang.Class类实例。
如果找不到类,则返回 ClassNotFoundError异常。该方法的逻辑就是双亲委派机制
findClass(String name)查找二进制名称为name的类,返回结果为java.lang.Class类实例。受保护的方法
defineClass(String name,byte[] b,int off,int len)
根据给定的字节码数组b转换为Class的实例,off和len参数表示实际Class信息在byte数组位置和长度。
findClass()方法通常是和defineClass方法一起用的,一般情况下,自定义类加载器,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()方法生成类的Class对象。
resolvecClass()链接指定的Java类。使用该方法可以使用类的Class对象创建完成的同时也被解析。
B:SecureClassLoader 与 URLClassLoader
SecureClassLoader扩展了 ClassLoader,新增了几个与使用相关的代码源和权限定义类验证的方法,一般我们不会直接跟类打交道,更多是与它的子类URLClassLoader有所关联。
在编写自定义类加载器时,如果没有太过复杂的需求,可以直接继承URLClassLoader类,这样避免了编写findClass()
方法及其字节码流的方式,时自定义加载器编写更加简洁。
URLClassLoader重写、实现了findClass()方法
AppClassLoader重写了loaderClass(String ,boolean)方法。
ExtClassLoader并没有重写loaderClass()方法,说明遵循了双亲委派机制, AppClassLoader重写了loaderClass(String ,boolean)方法也说明了。
C:ExtClassLoader 与 AppClassLoader
AppClassLoader重写了loaderClass(String ,boolean)方法。
ExtClassLoader并没有重写loaderClass()方法,说明遵循了双亲委派机制, AppClassLoader重写了
loaderClass(String ,boolean)方法也说明了。


D:Class.forName() 与 ClassLoader.LoadClass()
a,Class.forName(): 是一个静态方法,常用Class.forName(String className),该方法将 Class 文件加载到内存的同时,会 执行类的初始化。
b,ClassLoader.loadClass():这是一个实例方法,需要 ClassLoader 对象来调用该方法。该方法将 Class 文件加载到内存中,并不会执行类的初始化,直到这个类第一次使用时才进行初始化。
双亲委派机制优势和劣势
1,定义
如果一个类加载器在接到加载类的请求时,它首次不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载去完成,依次递归,如果父类加载器可以完成类加载任务,就会返回。只有父类加载器无法完成此加载任务是,自己去加载。
2,本质
规定类加载的顺序是:引导类加载器先加载,若加载不到,由扩展类加载器加载,若还加载不到,才会有系统类加载器或自定义类加载器进行加载。

3,优势
避免类的重复加载,确保一个类的全局唯一性
Java类随着它的类加载器一起具备一种带有优先级的层次关系,通过这种层级关系可以避免类的重复使用,已经加载了该类时,就没有必要子ClassLoader在加载一次。
保护程序安全,防止核心API被随意篡改。
4,代码支持
先在当前加载器的缓存中查找有无目标类,如果有,直接返回
判断当前加载器的父加载器是否为空,如果不为空,则调用parent.loadClass(name,false)接口进行加载
反之,如果当前加载器的父类加载器为空,则调用findBootstrpClassOrNull(name)接口,让引导类加载器进行加载
如果通过以上3条路径没有成功加载,则调用ffindClass(name)接口进行加载。最后调用defineClass方法
5,缺点
应用类访问系统类自然是没有问题,但是系统类访问应用类就会出现问题
6,结论
由于Java虚拟机规范并没有明确要求类加载类的加载机制一定要使用双亲模型,只是建议采用这种方式而已。
A,破坏双亲委派机制1
为了兼容这些已有的代码,无法在以技术手段loadClass()被子类覆盖的可能性,只能在在后面添加新的protected方法findClass(),并引导用户编写的类加载逻辑是尽可能去重写这个方法。
B,破坏双亲委派机制2 --- 线程上下文类加载器
越基础的类由越上层的加载器进行加载,
启动类加载器是绝不可能认识,加载这些代码的,那该怎么办?
SPI:在java中,通常把核心类rt.jar中提供外部服务,可由应用层自行实现的接口为SPI。
这是一种“舞弊行为”,这种父类加载器去请求子类加载器完成类加载的行为,这种行为实际上打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型的一般性原则。

C:破坏双亲委派机制3
由于用户对程序动态性的追求而导致。如:代码替换(Hot Swap),模块热部署(Hot Deployment)
1,将以java.*开头的类,委派给父类加载器加载。
2,否则,将委派列表名单内的类,委派给父类加载器加载。
D:小结
这里的被破坏并不一定是带有贬义。只有明确的目的和充分的理由,突破旧有的原则无疑是一种创新。
E,热替换的实现
热替换指在程序的过程中,不停止服务,只通过替换程序文件来修改程序的行为。热替换的关键需求在于服务不能中断,修改必须立即表现正在运行的系统中。天生支持热替换的:PHP
但是Java并不是天生的,需要通过修改类文件,并无法让系统再来加载并重定义的类。

沙箱安全机制
保证程序安全
保护Java原生的JDK代码
Java安全模型的核心就是Java沙箱。什么是沙箱?沙箱是一个限制程序运行的环境。
沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问。
沙箱主要限制系统资源访问,那么系统资源是什么?CPU,内存,文件系统,网络。不同级别的沙箱对这些资源访问机制也可以不一样。
所有的Java程序运行都可以指定沙箱,可以定制安全策略。



*
自定义类的加载器
- 隔离加载类
- 修改类加载的方式
- 扩展加载源
- 防止源码泄漏
注意:
一般情况下,使用不同的类加载器去加载不同的功能模块,会提高应用程序的安全性。但是,如果涉及Java类型转换,
则加载器反而容易产生不完美事情。在做Java类型转换时,只有两个类型都会由同一个加载器所加载,才能进行类型转换,否则转换时会发生异常。
JDK9新特性
1,扩展机制被移除,扩展类加载器由于向后兼容性的原因被保留,不过被重命名为类加载器。可以通过ClassLoader的新方法getPlatformClassLoader()来获取。
2,平台类加载器和应用程序类加载器都不在基础自 java.net.URLClassLoader。现在启动类加载器,平台类加载器,应用程序类加载器全部继续于 jdk.internal.loader.BuiltinClassLoader。

3,类加载有了名称。该名称在构造方法中指定,可以通过getName()方法来获取。平台类加载的名称为platform,应用类加载器名称是app。类加载器的名称在调试与类加载器相关的问题是会非常有用。
4,启动类加载器现在是在jvm内部和java类库共同协作实现的类加载器,但为了与之前代码兼容,在获取启动类加载器的场景中仍然会返回null,而不会得到BootClassLoader实例。
5,类加载的委派关系也发生变动

posted on 2022-04-10 22:32 只想做加法(ylc) 阅读(165) 评论(0) 收藏 举报
浙公网安备 33010602011771号