什么是ClassLoader
ClassLoader 做什么的?
- class Class<T> {
- ...
- private final ClassLoader classLoader;
- ...
- }
延迟加载
各司其职
JVM 运行实例中会存在多个 ClassLoader,不同的 ClassLoader 会从不同的地方加载字节码文件。它可以从不同的文件目录加载,也可以从不同的 jar 文件中加载,也可以从网络上不同的静态文件服务器来下载字节码再加载。
JVM 中内置了三个重要的 ClassLoader,分别是 BootstrapClassLoader、ExtensionClassLoader 和 AppClassLoader。
ClassLoader 传递性
双亲委派
这三个 ClassLoader 之间形成了级联的父子关系,每个 ClassLoader 都很懒,尽量把工作交给父亲做,父亲干不了了自己才会干。每个 ClassLoader 对象内部都会有一个 parent 属性指向它的父加载器。
- class ClassLoader {
- ...
- private final ClassLoader parent;
- ...
- }
Class.forName
当我们在使用 jdbc 驱动时,经常会使用 Class.forName 方法来动态加载驱动类。
- Class.forName("com.mysql.cj.jdbc.Driver");
其原理是 mysql 驱动的 Driver 类里有一个静态代码块,它会在 Driver 类被加载的时候执行。这个静态代码块会将 mysql 驱动实例注册到全局的 jdbc 驱动管理器里。
- class Driver {
- static {
- try {
- java.sql.DriverManager.registerDriver(new Driver());
- } catch (SQLException E) {
- throw new RuntimeException("Can't register driver!");
- }
- }
- ...
- }
forName 方法同样也是使用调用者 Class 对象的 ClassLoader 来加载目标类。不过 forName 还提供了多参数版本,可以指定使用哪个 ClassLoader 来加载
- Class<?> forName(String name, boolean initialize, ClassLoader cl)
通过这种形式的 forName 方法可以突破内置加载器的限制,通过使用自定类加载器允许我们自由加载其它任意来源的类库。根据 ClassLoader 的传递性,目标类库传递引用到的其它类库也将会使用自定义加载器加载。
自定义加载器
ClassLoader 里面有三个重要的方法 loadClass()、findClass() 和 defineClass()。
- class ClassLoader {
- // 加载入口,定义了双亲委派规则
- Class loadClass(String name) {
- // 是否已经加载了
- Class t = this.findFromLoaded(name);
- if(t == null) {
- // 交给双亲
- t = this.parent.loadClass(name)
- }
- if(t == null) {
- // 双亲都不行,只能靠自己了
- t = this.findClass(name);
- }
- return t;
- }
- // 交给子类自己去实现
- Class findClass(String name) {
- throw ClassNotFoundException();
- }
- // 组装Class对象
- Class defineClass(byte[] code, String name) {
- return buildClassFromCode(code, name);
- }
- }
- class CustomClassLoader extends ClassLoader {
- Class findClass(String name) {
- // 寻找字节码
- byte[] code = findCodeFromSomewhere(name);
- // 组装Class对象
- return this.defineClass(code, name);
- }
- }
- // ClassLoader 构造器
- protected ClassLoader(String name, ClassLoader parent);
双亲委派规则可能会变成三亲委派,四亲委派,取决于你使用的父加载器是谁,它会一直递归委派到根加载器。
Class.forName vs ClassLoader.loadClass
这两个方法都可以用来加载目标类,它们之间有一个小小的区别,那就是 Class.forName() 方法可以获取原生类型的 Class,而 ClassLoader.loadClass() 则会报错。
- Class<?> x = Class.forName("[I");
- System.out.println(x);
- x = ClassLoader.getSystemClassLoader().loadClass("[I");
- System.out.println(x);
- ---------------------
- class [I
- Exception in thread "main" java.lang.ClassNotFoundException: [I
- ...
钻石依赖
项目管理上有一个著名的概念叫着「钻石依赖」,是指软件依赖导致同一个软件包的两个版本需要共存而不能冲突。
我们平时使用的 maven 是这样解决钻石依赖的,它会从多个冲突的版本中选择一个来使用,如果不同的版本之间兼容性很糟糕,那么程序将无法正常编译运行。Maven 这种形式叫「扁平化」依赖管理。
- $ cat ~/source/jcl/v1/Dep.java
- public class Dep {
- public void print() {
- System.out.println("v1");
- }
- }
- $ cat ~/source/jcl/v2/Dep.java
- public class Dep {
- public void print() {
- System.out.println("v1");
- }
- }
- $ cat ~/source/jcl/Test.java
- public class Test {
- public static void main(String[] args) throws Exception {
- String v1dir = "file:///Users/qianwp/source/jcl/v1/";
- String v2dir = "file:///Users/qianwp/source/jcl/v2/";
- URLClassLoader v1 = new URLClassLoader(new URL[]{new URL(v1dir)});
- URLClassLoader v2 = new URLClassLoader(new URL[]{new URL(v2dir)});
- Class<?> depv1Class = v1.loadClass("Dep");
- Object depv1 = depv1Class.getConstructor().newInstance();
- depv1Class.getMethod("print").invoke(depv1);
- Class<?> depv2Class = v2.loadClass("Dep");
- Object depv2 = depv2Class.getConstructor().newInstance();
- depv2Class.getMethod("print").invoke(depv2);
- System.out.println(depv1Class.equals(depv2Class));
- }
- }
在运行之前,我们需要对依赖的类库进行编译
- $ cd ~/source/jcl/v1
- $ javac Dep.java
- $ cd ~/source/jcl/v2
- $ javac Dep.java
- $ cd ~/source/jcl
- $ javac Test.java
- $ java Test
- v1
- v2
- false
在这个例子中如果两个 URLClassLoader 指向的路径是一样的,下面这个表达式还是 false,因为即使是同样的字节码用不同的 ClassLoader 加载出来的类都不能算同一个类
- depv1Class.equals(depv2Class)
我们还可以让两个不同版本的 Dep 类实现同一个接口,这样可以避免使用反射的方式来调用 Dep 类里面的方法。
- Class<?> depv1Class = v1.loadClass("Dep");
- IPrint depv1 = (IPrint)depv1Class.getConstructor().newInstance();
- depv1.print()
分工与合作
这里我们重新理解一下 ClassLoader 的意义,它相当于类的命名空间,起到了类隔离的作用。位于同一个 ClassLoader 里面的类名是唯一的,不同的 ClassLoader 可以持有同名的类。ClassLoader 是类名称的容器,是类的沙箱。
Thread.contextClassLoader
如果你稍微阅读过 Thread 的源代码,你会在它的实例字段中发现有一个字段非常特别
- class Thread {
- ...
- private ClassLoader contextClassLoader;
- public ClassLoader getContextClassLoader() {
- return contextClassLoader;
- }
- public void setContextClassLoader(ClassLoader cl) {
- this.contextClassLoader = cl;
- }
- ...
- }
contextClassLoader「线程上下文类加载器」,这究竟是什么东西?
- Thread.currentThread().getContextClassLoader().loadClass(name);
这意味着如果你使用 forName(string name) 方法加载目标类,它不会自动使用 contextClassLoader。那些因为代码上的依赖关系而懒惰加载的类也不会自动使用 contextClassLoader来加载。
什么是ClassLoader的更多相关文章
- 使用自定义 classloader 的正确姿势
详细的原理就不多说了,网上一大把, 但是, 看了很多很多, 即使看了jdk 源码, 说了罗里吧嗦, 还是不很明白: 到底如何正确自定义ClassLoader, 需要注意什么 ExtClassLoade ...
- Atitti 载入类的几种方法 Class.forName ClassLoader.loadClass 直接new
Atitti 载入类的几种方法 Class.forName ClassLoader.loadClass 直接new 1.1. 载入类的几种方法 Class.forName ClassLo ...
- java笔记--理解java类加载器以及ClassLoader类
类加载器概述: java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制 ...
- Class.forName和ClassLoader.loadClass等
Class类 首先,Class类里可以记载所有类的属性.方法等信息.这个也就是运行时类别标记,它记录了所有的对象(比如int,MyClass,void,数组等等)对应的类信息. Class对象 JVM ...
- Java ClassLoader 原理详细分析(转)
转载自:http://www.codeceo.com/article/java-classloader.html 一.什么是ClassLoader? 大家都知道,当我们写好一个Java程序之后,不是管 ...
- [Tomcat] Tomcat的classloader
定义 同其他服务器应用一样,tomcat安装了各种classloader(classes that implement java.lang.ClassLoader) Bootstrap | Syste ...
- java中Class.forName("xxx")和ClassLoader().loadClass("xxx")的区别
一.首先,查看Class类中的forName方法,可以发现有如下三个方法,但是我们通常用的是只有一个参数的方法. 简单介绍一下这三个方法: 第一个方法Class.forName("xxx&q ...
- java.lang.Class.forName(String name, boolean initialize, ClassLoader loader)方法
描述 Java.lang.Class.forName(String name, boolean initialize, ClassLoader loader) 方法返回与给定字符串名的类或接口的Cla ...
- 深入分析Java ClassLoader原理
一.什么是ClassLoader? 大家都知道,当我们写好一个Java程序之后,不是管是CS还是BS应用,都是由若干个.class文件组织而成的一个完整的Java应用程序,当程序在运行时,即会调用该程 ...
- 深入分析ClassLoader
首先介绍下ClassLoader: ClassLoader顾名思义就是类加载器,负责将Class加载到JVM中,事实上ClassLoader除了能将Class加载到JVM中之外,还有一个重要的作用就是 ...
随机推荐
- IP判断 (字符串处理)
关于IP合法性判断的题目,每个oj上的约束条件不尽相同,我就根据自己做过的题目吧所有的约束条件汇总到一块,到时候做题时只需要把多余的越是条件删掉即可 题目描述: 对于IP我们总会有一定的规定,合法的I ...
- Test plan
Options for Test Strategy: 1. Regular test: all the planned test cases will be executed 2. Extented ...
- 【Python学习】request库
Requests库(https://www.python-requests.org/)是一个擅长处理那些复杂的HTTP请求.cookie.header(响应头和请求头)等内容的Python第三方库. ...
- Linux进程的创建函数fork()及其fork内核实现解析【转】
转自:http://www.cnblogs.com/zengyiwen/p/5755193.html 进程的创建之fork() Linux系统下,进程可以调用fork函数来创建新的进程.调用进程为父进 ...
- Python抓取微博评论(二)
对于新浪微博评论的抓取,首篇做的时候有些考虑不周,然后现在改正了一些地方,因为有人问,抓取评论的时候“爬前50页的热评,或者最新评论里的前100页“,这样的数据看了看,好像每条微博的评论都只能抓取到前 ...
- 如何去除decimal后面的零?
如何去除decimal后面的零? 1.260000m.ToString("G29") 不显示科学记数法? decimal.Parse("0.0000001",S ...
- fastdfs5.10 centos6.9 安装配置
下载相关软件 https://codeload.github.com/happyfish100/fastdfs/tar.gz/V5.10http://download.csdn.net/detail/ ...
- APS高级计划排程系统应该支持的企业应用场景
APS高级计划排程系统应该支持的企业应用场景 面对工业4.0智能制造的挑战,很多企业希望能够引进APS高级计划排程系统,全自动的.快速的制定精细化的生产计划,准确的计算产线/设备上各种产品型号的加工顺 ...
- CentOS7.6打开的程序窗口居中
每次在CentOS7中打开新的窗口都会靠左上角显示,所以每次打开一个窗口都要多做一步操作,将窗口移到屏幕中间来,强迫症的我觉得太麻烦了,所以… 安装ccsm yum -y install compi ...
- openssl asn.1 生成DER文件,把DER文件转换成内部数据结构
1 在实现之前,先来介绍如何生成der文件,有了源数据才能进行验证和测试.生成的方法是使用在openssl的命令中使用*asn1parse*根据配置文件来生成.详情如下: 1.1 创建配置文件test ...