类加载器详解
文章目录
类加载器
安全作用
类加载器是Java中的一道防线,由它将代码装入jvm中,其中也包括有危险的代码。它的安全作用有三点:
- 保护善意代码不受恶意代码的干扰
- 保护已验证的类库
- 代码放入有不同的行为限制的各个保护域中
分类
- 启动类加载器
- 扩展类加载器
- 应用程序类加载器
- 用户自定义的类加载器
双亲委派模型
每个类在加载其他类时会使用加载自身的类加载器去加载。
而当类加载器去加载一个类的时候,先将类加载委托给父类加载器,若父类加载器无法加载,再由此类加载器再进行加载。类似于责任链设计模式。
委派顺序
启动类加载器-扩展类加载器-应用程序类加载器-用户自定义类加载器
破坏双亲委派
jdbc需要使用不同数据库厂商的驱动,而这个驱动的jar包由用户加入到项目路径中。但是jdbc通过spi机制在DriverManager类中加载驱动时,因为DriverManager类是由启动类加载器加载的,所以其去加载驱动时,驱动jar包不在启动类加载器的加载范围内,所以无法加载。因此DriverManager类使用了线程上下文类加载器来加载驱动类。
系统类加载器
系统类加载器是由java.system.class.loader系统变量指定的,如果没有指定则在ClassLoader.initSystemClassLoader()方法中通过getBuiltinAppClassLoader()方法返回ClassLoaders.appClassLoader(),也就是应用程序类加载器来充当系统类加载器。
线程上下文类加载器
在创建线程Thread对象时此线程会继承父线程的上下文类加载器,而默认的线程上下文类加载器是系统类加载器。
线程上下文类加载器为什么默认是系统类加载器
JVM会在启动时调用System.类中initPhase3方法获取系统类加载器,并将其设置到启动线程的线程上下文类加载器中,因此子线程就会继承此系统类加载器。
private static void initPhase3() {
ClassLoader scl = ClassLoader.initSystemClassLoader();
Thread.currentThread().setContextClassLoader(scl);
}
类加载器的命名空间
class对象的唯一性
一个类类型的唯一性或者说class对象的唯一性,是由类的全限定名和类加载器决定。
因此启动类加载器加载的String类型和用户自定义类加载器加载的String类尽管全限定名相同也不是同一个class(不考虑双亲委派),而且无法进行类型强转。
如果正常双亲委派的话,子类加载器是无法加载一个父类加载器加载了的同限定名的类的。(限定名为全包名+类名)
命名空间
我们自己写的类都是由系统类加载器负责加载,这些类的信息都保存在系统类加载器的命名空间中,也存在于它的子类加载器的命名空间中。
命名空间是由该类加载器加载的类以及其父类加载器加载的类所构成的,其中父类加载器加载的类对其子类可见,但是反过来子类加载的类对父类不可见,同一个命名空间中一定不会出现同一个类(全限定名一模一样的类)多个Class对象,换句话说就是在同一命名空间中只能存在一个Class对象。
因此常见的面试题是否可以自己写一个全限定名相同String类,在不破坏双亲委派模型的情况下是不可以的。
命名空间隔离
一个类无法使用不在加载它的类加载器命名空间中的类。
Tomcat如何实现各web应用隔离
Tomcat利用不同webapp都有自己的WebApp Classloader类加载器来实现命名空间的隔离。
因此不同的webapp中的依赖都由自己的WebApp类加载加载,不会干扰到其他webapp的依赖(webapp类加载器破坏了双亲委派,先自己加载加载不到再向上抛)。
又因为不同的WebApp Classloader类加载器实例的父类加载器又都相同,所以父类加载器加载的类对于多个webapp来说又是共享的。所以String类等等这些jdk提供的类又是多个webapp间共享的。
运行包
同一个类加载器加载的同一个包中的所有类型就是一个运行包。
在允许同一个包中的两个类型访问之前,jvm要确信此两个类型是由同一个类加载器加载的(同一个运行包内)。
因此你自己创建的java.util.xxx类是由系统类加载器加载的,而jdk中的java.util.xxx类是由启动类加载器加载的,所以你的类与jdk的类不在同一个运行包内,无法访问对方的default、protect访问级别的方法和属性。
注意区分运行包和命名空间的区别,命名空间子类拥有父类的命名空间,而运行包都是自己加载的类。