findClass方法是JDK1.2后提出的,目的是为了保证加载的类符合双亲委派模型。而loadClass方法一直存在,没有实现双亲委派模型,不过可以让我们自己去实现。
对于JVM而言,能够加载的类都是将.java文件通过编译后的字节码加载入内存中的。而编译后的文件,也就是通过javac命令编译成的.class文件,终究是以流的形式变成byte[]的数组,再通过ClassLoader的defineClass方法使其最终加载入内存,并可以在代码中获取到对应的Class类。
在这个过程中,对于defineClass而言,这个byte数组是如何获取到的毫不关心。比如,可以通过从文件中读取,可以从网络下载,可以是将加密后的数据解密后的结果,甚至可以根据实际使用场景new出一个byte数组,自己往数组中写入数据都是可以的。
但是,传入defineClass的字节byte数组,一定要符合class文件的结构和规则。正是因为虚拟机这样的灵活的加载方式,才实现了诸多灵活多变的技术。比如,早年的Applet以及当下Android的热更新;又或者像Javassist针对JVM生成class的框架或者dexmaker针对Dalvik生成dex的框架,可以在程序运行时生成想要的类,随之加载入内存等等。
这样做就带来了一个问题,在不做特殊处理的情况下,任何类都是可以通过自定义的ClassLoader加载的,如果这个类之前已经被父ClassLoader加载过,内存中就会有多个具有相同全限定名的类被加载到内存中。
内存中存在多个相同全限定名的类也并非不好,只是根据实际应用场景,比如像Object的类,我们是不希望内存中存在多个的,如果父ClassLoader已经加载过,我们拿来用就好。而且重复加载Object,也会在类判定上带来问题,因为判断两个类是否一致,不仅要看全限定名是否相同,还要看是否具有相同的ClassLoader。
但是对于某些场景,比如自定义的ClassLoader,我们允许一些类可以被不同ClassLoader加载,这时即使不遵循双亲委派模型也是可以的。
综上,双亲委派模型并不是一定要遵循的标准,它只是用来解决当我们不希望内存中存在多个具有相同全限定名加载类的情况。同时,将自定义ClassLoader可以加载的类仅限定在特定的类上,其他的类仍然按照原有的加载方式。
下面的代码是ClassLoader的loadClass方法。如果我们重写的是findClass,只有当父ClassLoader都没有加载某个类时,才会去调用c=findClass(name)。也就是说,findClass方法实际上是ClassLoader留给我们去重写的,如果希望遵循双亲委派模型的话。
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 {
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
}
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;
}
}
假如我们铁了心了就是想要不遵循双亲委派模型,可以直接去重写loadClass。但是这样做也有需要解决的问题,比如一个类Hello,该类依赖java.lang.Object和java.lang.String方法。
public class Hello {
public static String getMessage() {
return "hello";
}
}
如果在自定义ClassLoader时按照如下写法加载Hello时,自定义ClassLoader无法加载Object和String,则会报出NoClassDefFoundError。对于这种情况,手动地将调用super.loadClass,以便让父ClassLoader去加载Object和String的类。
public Class<?> loadClass(String name) throws ClassNotFoundException {
System.out.println("load:" + name);
byte[] bytes = paraseClassByName(name);
if (bytes == null) {
System.out.println("load failed:" + name);
return null;
}
return defineClass(name, bytes, 0, bytes.length);
}
关于ClassLoader的Demo,里面对findClass和defineClass的编写了demo,用于理解双亲委派模型。