类加载
class文件的显式加载与隐式加载的方式是指JVM加载class文件到内存的方式。
显式加载:显式加载指的是在代码中通过调用ClassLoader加载class对象,如直接使用class.forName(name)或this.getClass().getclassLoader().loadClass()加载class对象。
隐式加载:隐式加载则是不直接在代码中调用ClassLoader的方法加载class对象,而是通过虚拟机自动加载到内存中,如在
加载某个类的class文件时,该类的class文件中引用了另外一个类的对象,此时额外引用的类将通过JVM自动加载到内存中。
- 何为类的唯一性?
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确认其在Java虚拟机中的唯一性。每一个类加载器,都拥有一个独立的类名称空间:比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义。否则,即使这两个类源自同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。
命名空间
-
每个类加载器都有自己 的命名空间,命名空间由该加载器及所有的父加载器所加载的类组成
-
在同一命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
-
在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类
类加载机制特征
双亲委派模型
但不是所有类加载都遵守这个模型,有的时候,启动类加载器所加载的类型,是可能要加载用户代码的,比如3DK内部的ServiceProvider/ServiceLoader机制,用户可以在标准API框架上,提供自己的实现,DK也需要提供些默认的参考实现。例如,Java 中NDI、3DBC、文件系统、Cipher等很多方面,都是利用的这种机制,这种情况就不会用双亲委派模型去加载,而是利用所谓的上下文加载器。
可见性
子类加载器可以访问父加载器加载的类型,但是反过来是不允许的。不然,因为缺少必要的隔离,我们就没有办法利用类加载器去实现容器的逻辑。
单一性
由于父加载器的类型对于子加载器是可见的,所以父加载器中加载过的类型,就不会在子加载器中重复加载。但是注意,类加载器“邻居”间,同一类型仍然可吼被加载多次,因为互相并不可见。
加载器分类
- 除了顶层的启动类加载器外,其余的类加载器都应当有自己的“父类”加载器。
- 不同类加载器看似是继承(Inheritance)关系,实际上是包含关系。在下层加载器中,包含着上层加载器的引用。如:
class ClassLoader{
ClassLoader parent; //父类加载器
public classLoader(classLoader parent){
this.parent = parent;
}
}
class ParentClassLoader extends ClassLoader{
public ParentClassLoader(classLoader parent){
super(parent);
}
}
class ChildclassLoader{
ClassLoader parent; //父类加载器
public childClassLoader(ClassLoader parent){ // parent = new ParentClassLoader();
this.parent = parent;
}
}
ClassLoader与现有类加载器的关系
自定义类加载器
Java提供了抽象类java.lang.ClassLoader,所有用户自定义的类加载器都应该继承ClassLoader类。在自定义ClassLoader的子类时候,常见的会有两种做法:
- 方式一:重写loadClass()方法 (实现双亲委派在该方法)
- 方式二:重写findClass()方法 (实际调用defineClass()方法加载)
双亲委派机制
双亲委派jdk1.2引入。
优势
- 避免类的重复加载,确保一个类的全局唯一性
- Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
- 保护程序安全,防止核心API被随意篡改
弊端
- 检查类是否加载的委托过程是单向的,这个方式虽然从结构上说比较清晰,使各个ClassLoader的职责非常明确,但是同时会带来一个问题,即顶层的ClassLoader无法访问底层的ClassLoader所加载的类。
- 通常情况下,启动类加载器中的类为系统核心类,包括一些重要的系统接口,而在应用类加载器中,为应用类。按照这种模式,应用类访问系统类自然是没有问题,但是系统类访问应用类就会出现问题。比如在系统类中提供了一个接口,该接口需要在应用类中得以实现,该接口还绑定一个工厂方法,用于创建该接口的实例,而接口和工厂方法都在启动类加载器中。这时,就会出现该工厂方法无法创建由应用类加载器加载的应用实例的问题。
结论:由于Java虚拟机规范并没有明确要求类加载器的加载机制一定要使用双亲委派模型,只是建议采用这种方式而已。