1. 概述
ClassLoader的作用:通过各种方式将Class信息的二进制数据流读入到JVM内部,转换为一个与目标类对应的java.lang.Class对象实例,然后交给Java虚拟机进行链接、初始化操作;ClassLoader只会影响到类的加载, 不会改变类的链接和初始化行为;至于是否可以运行,则由Execution Engine决定;
- 类加载的分类
显式加载 & 隐式加载
显示加载是指在代码中通过调用ClassLoader的方法来加载class对象,如直接使用Class.forName()或this.getClass.getClassLoader().loadClass()加载Class对象;
隐式加载是指不在代码中通过调用ClassLoader的方法来加载class对象,而是通过虚拟机自动加载到内存中,例如在加载一个类的class文件时,文件中又引用了另外一个类的对象,此时额外引用的类将通过JVM加载到内存中;
- 命名空间
1). 何为类的唯一性?
对于任意一个类,都需要由加载该类的类加载器和类本身一同确认其在Java虚拟机中的唯一性;每一个类加载器,都拥有一个独立的类名称空间,比较两个类是否相等需要两个类都是由同一个类加载器加载的前提下才有意义;
2). 命名空间
- 每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父类加载器所加载的类组成;
- 在同一命名空间中,不会出现类的完整名字都相同的两个类;
- 在不同的命名空间中,可能会出现类的完整名字都相同的两个类;
- 类加载机制的三个基本特征:
1). 双亲委派模型
2). 可见性:子类加载器可以访问父类加载器加载的类型,但是反过来是不允许的;
3). 单一性:由于父类加载器加载的类型对子类可见,因此父类加载器加载过的类型不会在子类加载器中重复加载;
2. 类加载器的分类
1). 引导类加载器(Bootstrap ClassLoader)
- 该类的加载使用C/C++语言实现的,嵌套在JVM内部;
- 用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容)。用于提供JVM自身需要的类;
- 并不继承自java.lang.Classloader,没有父加载器;
- 出于安全考虑,BootStrap启动类加载器只会加载包名为java、javax、sun等开头的类;
- 加载扩展类或应用程序类加载器,并指定为它们的父类加载器;
2). 扩展类加载器(Extention ClassLoader)
- Java语言编写,由sun.misc.Launcher$ExtClassLoader实现;
- 继承于ClassLoader类;
- 父类加载器为启动类加载器;
- 从java.ext.dirs系统属性指定的目录下加载类库,或者从JDK的安装目录jre/lib/ext子目录下加载类库;
3). 系统类加载器(Application ClassLoader)
- Java语言编写,由sun.misc.Launcher$AppClassLoader实现;
- 继承于ClassLoader类;
- 父类加载器为扩展类加载器;
- 负责加载环境变量classpath或系统属性java.class.path指定路径下的类库;
- 应用程序中类的加载器默认是系统类加载器;
- 用户自定义类加载器的父类加载器;
- 可以通过ClassLoader的getSystemClassLoader()方法来获取到该类加载器;
4). 用户自定义类加载器
自定义类加载器能够实现应用隔离,例如Tomcat、Spring等中间件和组件框架都在内部实现了自定义的加载器,并且通过自定义加载器隔离不同的组件模块,这种机制远优于C/C++,想不修改C/C++程序为其新增功能是几乎不可能的;
3. ClassLoader源码解析
ClassLoader的继承树关系:
abstract ClassLoader - > SecureClassLoader -> URLClassLoader -> ExtClassLoader/AppClassLoader
ClassLoader中的重要方法:loadClass(String); resolveClass(Class<?>); findClass(String); defineClass(byte[],int,int);
URLClassLoader中重写了findClass()方法;
AppClassLoader中重写了loadClass()方法;
1). ClassLoader中方法:
loadClass():根据类的全路径名加载类,返回结果为Class类的实例;如果找不到类,则返回ClassNotFoundException异常;方法中的逻辑为双亲委派模型的实现;
- 同步操作,保证只能够加载一次;
- findLoadedClass(),在缓存中判断是否已经加载同名的类;
- 获取当前类加载器的父类加载器,调用父类加载器的loadClass()方法,进行类的加载,递归调用load Class()方法;
- 当父类加载器为BootstrapClassLoader,调用findBootstrapClassOrNull()方法,在缓存中查找,是否由引导类加载器加载;
- 父类加载器不加载,则返回null;由当前类的加载器通过loadClass()方法加载类;
findClass():根据类的全路径名,找到对应的类,返回java.lang.Class类的实例;JVM鼓励我们重写该方法,因为不会打破双亲委派机制;findClass()中会调用defineClass()方法;
defineClass():根据给定的二进制流的数据,返回Class实例;
resolveClass() : 执行解析Class的操作;
2). SecureClassLoader与URLClassLoader
SecureClassLoder扩展了ClassLoader,并且新增了几个与代码源和权限验证相关的方法;
URLClassLoader为ClassLoader中的部分空方法提供了具体的实现,包括:findClass()、findResource()等。此外,URLClassLoader还新增了URLClassPath类帮助协助获取class字节码流等功能;在编写自定义的类加载器时,如果没有过于复杂的需求,可以直接继承URLClassLoader类;
3). ExtClassLoader和AppClassLoader
这两个ClassLoader都继承自URLClassLoader,是sun.misc.Launcher类的静态内部类;ExtClassLoader没有重写loadClass方法,因此遵循双亲委派模式;而AppClassLoader重载了loadClass方法, 最终还是调用父类的loadClass()方法,因此也遵循双亲委派模式;
4). Class.forName()和ClassLoader.loadClass()
Class.forName():是一个静态方法,根据传入的类的全路径名返回一个Class对象实例;该方法在将Class文件加载到内存的同时,会执行类的初始化;
ClassLoader.loadClass():是一个实例方法,需要ClassLoader对象来调用该方法;该方法在将Class文件加载到内存的同时,不会执行类的初始化,而是第一次使用时才进行初始化操作;
4. 双亲委派模型
1). 定义与本质
定义:当一个类加载器收到加载类的请求时,自己不会去尝试加载该类,而是将请求委托给父类加载器去完成,依次递归,如果父类加载器可以完成加载任务,就成功返回;如果父类加载器不能完成加载任务,则又会依次将任务返回给子类加载器进行加载;
本质:类加载顺序:引导类加载器先加载,如果加载不到,则由扩展类加载器加载,如果还加载不到,则由系统类加载器或自定义类加载器加载;
2). 双亲委派模型的优势与劣势
优势:
- 避免类的重复加载,保证类全局的唯一性;
- 保护程序安全,防止核心API被随意篡改;
劣势:
- 顶层ClassLoader无法访问底层ClassLoader所加载的类;
Tomcat中采用的类加载机制和传统的双亲委派模型有所区别,当缺省的类加载器收到类的加载任务时,会首先由它自行加载,而当加载失败时,才会将加载任务委派给父类加载器去执行;
3). 破坏双亲委派模型
- 可以通过重写loadClass()方法,来自定义类加载的规则,从而打破双亲委派模型;
- 线程上下文类加载器:解决基础类调用用户代码的问题;该类加载器可以通过Thread类的setContextClassLoader()方法来进行设置;如果创建线程时未设置,则会从父进程中继承一个,如果应用程序在全局范围内都没有设置过,则类加载器默认会使用应用程序类加载器;实现父类加载器请求子类加载器完成类加载的行为;
- 代码热替换、模块热部署也打破双亲委派机制;
5. 沙箱安全机制
虚拟机会将所有代码加载到不同的系统域和应用域;系统域专门负责域关键资源进行交互,而应用域则通过系统域的部分代理来对各种需要的资源进行访问;系统中的不同受保护域,对应不一样的权限,存在于不同域的类文件则会拥有当前域的全部权限;
浙公网安备 33010602011771号