一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情。
1.类与类加载器
类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。这里所指的“相等”,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括使用instanceof关键字做对象所属关系判定等情况。
2.双亲委派模型
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
双亲委派机制目的: 为了防止内存中存在多份同样的字节码(安全)
对Java虚拟机来说,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。
对java开发人员来说 可以更细致地划分为三个 启动类加载器(Bootstrap ClassLoader)这个类将器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。
扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库
应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher $App-ClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
3.如何打破双亲委派模型以及打破双亲委派模型的案例
自定义ClassLoader,重写loadClass方法(只要不依次往上交给父加载器进行加载,就算是打破双亲委派机制)
tomcat打破双亲委派模型
CommonLoader类加载器:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容 器本身以及各个Webapp访问。
CatalinaLoader类加载器:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不 可见。
sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有 Webapp可见,但是对于Tomcat容器不可见。
WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前 Webapp可见,比如加载war包里相关的类,每个war包应用都有自己的WebappClassLoader,实现相互隔离,比如不同war包应用引入了不同的spring版本, 这样实现就能加载各自的spring版本。
在catalina.properties中配置目录
线程上下文加载器: jdbc的底层都是抽象接口,而且是位于rt.jar中,其实现肯定是由不同的数据库厂商来实现,那么问题就来了:这些标准都是由根类加载器所加载的,但是具体的实现是由具体的厂商来做的,那肯定是需要将厂商的jar放到工程的classpath当中来进行使用,很显然厂商的这些类是没办法由启动类加载器去加载,会由应用类加载器去加载,而根据“父类加载器所加载的类或接口是看不到子类加载器所加载的类或接口,而子类加载器所加载的类或接口是能够看到父类加载器加载的类或接口的”这一原则,那么会导致这样一个局面:比如说java.sql包下面的某个类会由启动类加载器去加载,该类有可能会要访问具体的实现类,但具体实现类是由应用类加载器所加载的,java.sql类加载器是根据看不到具体实现类加载器所加载的类的,这就是基于双亲委托模型所出现的一个非常致命的问题,这种问题不仅是在JDBC中会出现,在JNDI、xml解析等SPI(Service Provider Interface)场景下都会出现的,所以这里总结一下:父ClassLoader可以使用当前线程Thread.currentThread().getContextLoader()所指定的ClassLoader加载的类,这就改变了父ClassLoader不能使用子ClassLoader或者其它没有直接父子关系的ClassLoader加载的类的情况,既改变了双亲委托模型。线程上下文类加载器就是当前线程的Current ClassLoader。在双亲委托模型下,类加载是由下至上的,既下层的类加载器会委托上层进行加载。但是对于SPI来说,有些接口是Java核心库所提供的,而Java核心库是由启动类加载器来加载的,而这些接口的实现去来自于不同的jar包(厂商提供)。Java的启动类加载器是不会加载其它来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求。而通过给当前线程设置上下文类加载器,就可以由设置的上下文类加载器来实现对于接口实现类的加载。