ClassLoader是用来处理类加载的类,它管理着具体类的运行时上下文。 classloader是通过分层的关联方式来管理运行中使用的类,不同的classloader中管理的类是不相同的,或者即便两个类毫无二致(除了路径)也是不同的两个类,通俗的说就是,不同的类加载器加载的,即使是同一个类,也是不同的。所以,通过classloader的限制,我们可以建立不同的package路径以区别不同的类。那么也是因为有特定的classloader,我们可以实现具体模块的加载,而不影响jvm中其他类。
一.启动类加载器(BootstrapClassLoader)
负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即时放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被java程序直接引用。
BootstrapClassLoader用于在启动JVM时加载类,以使JVM能正常工作,因而它是用Native代码实现的,最早被创建出来。
二.扩展类加载器(ExtensionClassLoader)
负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用该类加载器。
Extension ClassLoader搜索特定的标准扩展目录。该标准扩展目录在不同的JVM实现中不一定相同,在sun的JVM中,它是%JRE_HOME%
\lib\ext,具体路径可以通过java.ext.dirs系统属性值获取。该标准扩展目录存在的目的在于扩展和共享,应用程序厂商可以将部分共享库放置于此,而不是各自程序的目录下的多份拷贝。在开发过程中,我们也可以把部分常用的库放置于此,而不必每次都去配置环境。
三.应用程序类加载器(ApplicationClassLoader)
负责加载用户路径上所指定的类库,开发者可以直接使用这个类加载器,也是默认的类加载器。SystemClassLoader搜索CLASSPATH中配置的目录和jar文件。
四.三种类加载器的关系与双亲委派
JVM在加载类时默认采用的是双亲委派机制。通俗的说,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载,除了顶层的启动累加载器外,其他的类加载器都有自己的父类加载器。要注意的是这些类加载器的父子关系一般不会以继承的关系来实现,而都是以组合的关系来复用父加载器的代码。
类加载器的顺序是:
先是bootstrap classloader,然后是extension classloader,最后才是system
classloader。从设计的角度,这其实就是一种职责链模式(Chain ofResponsibility Pattern)。
Java为什么需要这种模型呢?
答案是出于安全的考虑。Java是一种安全的编程语言,它会对每个加载的类执行安全检查,但是对Java核心库中的类是不执行安全检查的(即他们是受JVM信赖的)。(什么是安全检查?以我现在所知,就是用户可以通过securitymanager来控制特定目录的访问权限,如何使用这些控制将会是另一个主题。对安全检查是否还有其他的呢?)那么假如用户可以加载自己编写的和核心库同名的类(如java.lang.Object),那么这些用户编写的类就可以绕过安全检查,从而为一些恶意用户提供了一种破坏的途径。然而在使用这种代理模型后,类的加载首先会代理到BootstrapClassLoader中实行加载,如果它发现当前核心类库中可以加载对应的类,系统会加载核心库中的类,而不会加载用户编写的类。
但是如果只是这样,还不足以保证类的加载的安全。因为用户完全可以定义自己的ClassLoader,在自己的ClassLoader中破坏这种代理模型,那么用户自己写的java.lang.Object就可以被加载了。为了解决这个问题,在Java中认为两个类名相同的实例,如果加载他们的ClassLoader不同,那么他们是不同的类型,即他们之间不能转换,而且用instanceof操作符返回的是false。这样的话,即使用户加载了自己的java.lang.Object类,它也不是系统认为可以信赖的java.lang.Object,那么它就绕不过安全检查机制。
五、其他
其实,双亲委派机制并不是一个强制性的约束模型,而是java设计者推荐给开发者的类加载器实现方式。在java中大部分的类加载器都遵循这个模型,但是有例外,比如:JDBC、JNDI、JCE、JAXB、JBI等。
现在大热的OSGI也违背了双亲委派的原则,使用了更加复杂的网站结构。因为OSGI实现模块化热部署的关键是自定义的类加载器,每一个模块都有自己的类加载器,更换Bundle时,把Bundle连同类加载器一起换掉来实现代码的热替换。
这些有关原理的东西,我们也要稍微了解一点,“不怕不知道就怕不知道”。了解了这些我们能更好的理解java的双亲委派机制和内部系统执行的流程。
不足之处,敬请指正。