在类的加载过程中的加载阶段,我们使用到了类加载器。
一、在虚拟机中,类的唯一性是怎样确保的。
在虚拟机中,是通过加载类的类加载器和类来唯一确定一个类的。所以就算连个不同的类加载器加载的class文件一模一样,这两个类在虚拟机中也不是同一个类。那么用instanceof、equeals()方法、isAssignableFrom()、isInstance()方法也得不到想要的结果。
例:
package fengli;
import java.io.IOException;
import java.io.InputStream;
public class Main {
public static void main(String[] args) throws Exception {
ClassLoader cl = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
try {
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException(name);
}
}
};
Object obj1=cl.loadClass("fengli.Main").newInstance();
Object obj2=new Main();
System.out.println(obj1.getClass());//class fengli.Main
System.out.println(obj2.getClass());//class fengli.Main
System.out.println(obj1 instanceof fengli.Main);//false
System.out.println(obj2 instanceof fengli.Main);//true
}
}
运行结果:
二、双亲委派模型
从虚拟机角度,只存在两种不同的类加载器。
- 一种是启动类加载器(BootStrap ClassLoader),这个类加载器使用C++实现,是虚拟机的一部分。
- 另一个就是其他类加载器。都继承与java.lang.ClassLoader。这是有java语言实现的,独立于虚拟机外部。
然而从开发人员角度,有三种类加载器。至于这三类加载器,我在:类的加载机制具体说道它们的区别了。
- 启动类加载器
- 扩展类加载器
- 应用程序类加载器,也称为系统类加载器。
双亲委派的类加载器之间的层次图。他们不是以继承关系组织的,而是用组合关系来复用父类加载器。
双亲委派的加载过程是这样的:
当用一个类加载器区加载Class文件时,先让其父类加载器区加载这个Class文件,当其父类不能加载这个Class文件时(在搜索范围没有找到这个类),才让这个类区加载Class文件。
器源码如下:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded(首先,检查这个类是否已经被加载)
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//如果没有,就尝试使用父类加载器区加载这个类(name)
if (parent != null) {
c = parent.loadClass(name, false);
} else {//如果不存在父类,就使用启动类加载器区加载这个类
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果父类加载器在搜索范围内没有对应的类,就调用自己的findClass方法进行加载。
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
使用双亲委派的好处是类随着它的类加载器一同具备了层次关系。因此Object类在双亲委派下,始终是同一个类。
三、破坏双亲委派模型
双亲委派并不是一种强制性的约束模型,而是java设计者推荐给开发者的一种类加载器实现方式。在历史上,双亲委派模型发生过三次大规模“被破坏”(并不具有贬义,指的是一种创新)情况。
第一次:添加了一个新的protected的finaClass().
第二次:是由于双亲委派的缺陷造成的,父类加载器不能调用子类加载器。于是使用线程上下文加载器区解决这个问题。
第三次:是由于用户对程序的动态性(代码热替换、模块热部署)的追求造成的。