类加载器

两个类来源于同一个 Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等

这里所指的“相等”,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括了使用instanceof关键字做对象所属关系判定等各种情况

如果自定义一个com.example.String会怎样?

可以自定义,项目中的String的优先级是同包下的String>java.lang包下的String>自定义包下的String,然后如果一个类中用到了两种String,需要指定好,比如main函数的入参String[] args

双亲委派模型:

  • 启动类加载器(Bootstrap Class Loader):负责加载存放在 \lib目录,或者被-Xbootclasspath参数所指定的路径中存放的, 而且是Java虚拟机能够识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机的内存中,其实就是JDK库里的
  • 扩展类加载器(Extension Class Loader):负责加载 \lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库(JDK9及以后,被重命名为平台类加载器Platform ClassLoader),可能是一些旧版的加密库等等,现在项目很少用到
  • 应用程序类加载器(Application Class Loader):负责加载用户类路径(ClassPath)上所有的类库,就是我们写的代码,例如com.example.xxx和第三方jar包

双亲委派模型:除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。不过这里类加载器之间的父子关系一般不是以继承(Inheritance)的关系来实现的,而是通常使用组合 (Composition)关系来复用父加载器的代码。

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时, 子加载器才会尝试自己去完成加载。

为什么使用双亲委派:

因为Java是确定一个唯一的类是根据类的全路径+类加载器确定的,对于同一个包名路径下的类,如果被不同类加载器加载出来,就会产生不同的类,这时程序的行为就不能保证了,例如,自己写一个java.lang.Object,并放在程序的ClassPath中,如果没有双亲委派,程序中就出现两个java.lang.Object类了(自己写的和java官方的,分别由应用程序类加载器和启动类加载器加载),但如果使用了双亲委派,即使一开始是由应用程序类加载器加载,最终也会委托给启动类加载器,启动类加载器根据类的全路径发现自己已经加载过这个类了,就不会再加载这个类,这就保证了核心类库里的类都是唯一且正确的,所以双亲委派模型保证了Java程序的稳定运作

为什么要向上委派,不能向下委派?

是否能一开始就由启动类加载器加载所有的类,启动类加载器发现不在自己加载范围内的类就交给自己的子类加载器?答案是不行的,因为一个类加载器可以只有一个父类加载器,需要加载类时直接向上委派就行,但如果是向下委派,同时又有多个子类加载器,这时候就不知道要委派给哪个子类加载器的(可能有多个子类加载器的原因是因为可以有自定义类加载器,加载器的组合是多样的,但不管怎样,只可能有一个父类加载器),这就好比一个二叉树,从叶子节点向上走,是有且只有一条唯一的路径的,而且这条路径的终点必然是根节点,而从根节点出发,一直向下走,是不能确定一条唯一路径的,虽然最终能到达某一叶子节点,但具体是哪个叶子节点取决于每次向下走的路线决策。而且向下委派意味着需要修改应用程序类加载器的源码。

破坏双亲委派模型:

自定义加载器的话,需要继承 ClassLoader 。如果我们不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass() 方法。

Tomcat采用自定义类加载器破坏了双亲委派,实现了Web应用之间的类的隔离

JDBC接口是jre下的,但实现是第三方供应商提供的,按理来说,一个类及其依赖类由同一个类加载器加载(确保这些类之间的依赖关系正确并保持一致),但这种情况下不会被同一个类加载器加载,这就需要用到线程上下文类加载器解决

线程上下文类加载器:当一个线程启动时,jvm会将应用类加载器赋值给当前线程的线程上下文类加载器,此时父类加载器就可以提高线程上下文类加载器获取到子类加载器,再由子类加载器加载父类找不到的类

什么时候需要自定义类加载器:

  • 想加载非classpath随意路径的类文件
  • 隔离同名类,需要不同应用下的同名类可以不冲突,如tomcat容器