类加载机制的加载阶段,是通过类加载器来加载的。
类加载器的模型。 双亲委派, 就是你要加载一个类的,首先会委派给父类的类加载器,看看这个类有没有加载。
不同的类加载器,实现加载不同位置的类。
当一个 JVM 启动的时候,Java 缺省开始使用如下三种类型类装入器:
启动(Bootstrap)类加载器: /lib 下面的类库加载到内存中
标准扩展(Extension)类加载器 它负责将 < Java_Runtime_Home >/lib/ext 或者由系统变量 java.ext.dir 指定位置中的类库加载到内存 开发者可以直接使用标准扩展类加载器
系统(System)类加载器 AppClassLoader 系统类路径(CLASSPATH)中指定的类库加载到内存
通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载
上面的三种类加载器 是虚拟机默认使用加载器。 每种加载器都默认加载不同位置的类到内存。
tomcat的例子分析:
也就是说,如果你的项目的class文件夹中的类加载 要委托给父类ExtClassLoade加载,继续递归,父类肯定加载不到,因为该class文件并没有在 < Java_Runtime_Home >/lib/ext 这个路径下, 就会返回给当前的类加载器加载,最终加载成功。
如果出现了包重复, 那么就会加载失败。 比如 tomcat的CATALINA_HOME/lib 库 是用一个叫common的加载器加载。如果用户自己库中有servlet-api 。
首先会委派给common WebappX发现了common加载了这个类 就会忽略掉用户库中servlet-api。
tomcat的每个应用都有自己的类加载器。 而普通的java程序 则使用的system类加载器,而tomcat的system则是加载tomcat启动的类。
类加载器与类本身一同确立java虚拟机的唯一性。虽然是这样,双亲委派模型就保证了任意一个类只被一个加载器所加载,也就是说jvm中类的唯一性。而且当你如果要写一个系统的类, 那么委派到顶层类加载器后, 就会被拒绝。因为已经有一个类了。
如果破坏了双亲委派模型的话。就需要自定义类加载器。比如tomcat 能够隔离多个项目的库 就是因为采用了不同的加载器。
几点规则:
Class.forName(String name)默认会使用 调用该代码的类的 类加载器来进行类加载。这种特性,证明了java类加载器中的名称空间是唯一的,不会相互干扰.保证同一个类中所关联的其他类都是由当前类的类加载器所加载的.
如何理解Class.forName(String name)默认会使用调用类的类加载器来进行类加载:
Class caller = Reflection.getCallerClass(3);
当自定义类加载器没有指定父类加载器的情况下,默认的父类加载器即为系统类加载器。即使用户自定义类加载器不指定父类加载器 ,同样是按照三个jvm默认的加载模型来加载。
将父类加载器强制设置为null,无法加载到ext下的库。那么会自动将boot类加载器设置为当前用户自定义类加载器的父类加载器 所以 不管用户 怎么做 jvm都保证 都必须要加载jre环境的库。要不然Object都无法识别了。
有一个前提是 自定义类加载器时 不要修改loadClass的执行逻辑。 而是修改findclass。这才能保证加载到jre环境的库。 这里面的机制没有必要深究。毕竟是人家代码的执行逻辑。
自定义类加载器的方法:
每个ClassLoader都维护了一份自己的名称空间,同一个名称空间里不能出现两个同名的类
不要覆写loadClass(…)方法 因为无法加载jre下类,而是使用findClass
使用线程上下文类加载器,可以在执行线程中,抛弃双亲委派加载链模式,使用线程上下文里的类加载器加载类。
大部分java app服务器(jboss, tomcat..)也是采用contextClassLoader来处理web服务。
线程上下文从根本解决了一般应用不能违背双亲委派模式的问题.
使用线程上下文加载类,也要注意,保证多根需要通信的线程间的类加载器应该是同一个,防止因为不同的类加载器,导致类型转换异常(ClassCastException).
在jdk中, URLClassLoader是配合findClass方法来使用defineClass,可以从网络或硬盘上加载class.
而使用类加载接口,并加上自己的实现逻辑,还可以定制出更多的高级特性.
JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识,不同类加载器加载的类将被置于不同的命名空间。也就是说,加载class文件生成 instanceKlass类型 对象,本来就是在不同命名空间。
osgi 类加载器的机制。实现热部署。我刚做的例子就是自定义一个类加载器去加载class文件。确实和之前的类是不在一个命名空间。 当你从代码中 通过new创建的对象 是当前的类加载器。 而如果要使用自定义类的加载器的对象,只能通过自定义类加载器进行load操作。