最近学了一下Java的底层类加载机制,总结了一下,如果有啥不对的地方,请各位看官老爷多多指正!
目录
一、Java中内置的三个加载器
引导类加载器:这个加载器的底层是通过c++代码实现的,它主要的功能是用来加载java核心类库,像是java.lang之类的。
扩展类加载器:该加载器主要是通过Java代码实现了,所以可以通过idea(笔者主要使用的IED工具)来进行相关代码的追踪。该加载器的功能是加载JRE中lib目录下的ext扩展目录的相关类库的。
应用程序加载器:该加载器和扩展类加载器一样,底层都是通过Java代码实现的。它主要负责加载用户自定义的放在类路径(classPath)下的类。
Java的双亲委派机制就是通过以上三个类之间的互相调用来实现的(加载器其实就是一个类)
二、双亲委派机制
双亲委派机制其实并不复杂,其实就是子类(应用程序加载器)查询自己所管理的加载区域内有没有加载过需要加载的类,没有的话就要父类(扩展类加载器)去查,然后父类(扩展类加载器)没有找到的话,再让祖父类(引导类加载器)去查找。然后祖父类(引导类加载器)如果没有在加载区域找到的话就会去对应的类路径下查找具体类文件进行加载。如果祖父类(引导类加载器)所管理的类路径下没有找到,就会安排父类(扩展类加载器)去它锁管理的类路径下去找,仍然没有找到的话,就让子类(应用程序加载器)去自己管理的类路径下去寻找,如果还是没有找到就报异常了。
其实就是一趟找东西的来回路径图,你在自己家里找东西没找到,然后去你爸家里找,然后还没有找到,就去你爷爷家找,然后还没有找到,然后你爷爷想起来,他在好像在银行保险柜里放了写东西,然后他就去拿回来,你看了一遍后,发现还是没有,就让你爸去银行保险柜了找找,仍然没有,最后去你自己的银行保险柜里找。(找没找到没关系,这一套来回下来就构成了双亲委派机制)
注意:三个加载器之间并不是相互继承的关系,只是互相调用的关系
三、双亲委派机制的优点
- 沙箱安全机制:双亲委派机制保证了Java核心类库中的核心内不会被用户自定义的类所取代,可以防止核心API被随意串改。
- 避免类被重复加载:当父类加载了需要加载的类之后,子类就不会再去加载了,保证了被加载类的唯一性。
四、打破双亲委派机制
1、为什么要打破双亲委派机制呢?
虽然双亲委派机制有很多优点,但对于某些特殊的需求,它却并不适用。比如Tomcat中运行spring版本不同的web应用时,由于不同版本类库中的一些细微的差别,而两个类库中的类名方法名又大体相同,所以就需要加载两个不同的类库来达成需求,因而就必须打破双亲委派机制了。
2、打破双亲委派机制的原理
主要是通过自定义加载器,重写ClassLoader类中的loadClass方法来打破双亲委派机制。
3、如何自定义加载器?
自定义加载器,需要继承ClassLoader类,并重写findClass方法。findClass方法主要是用来读取对应路径下的class类文件,并将之转化成JVM中的对象的。所以这一步不能省略。
以下代码是findClass方法的具体实现,仅供参考
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节 数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
4、重写loadClass方法打破双亲委派机制
/**
* 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/
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) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//用于过滤出需要打破双亲委派机制进行加载的类包
if(!name.startsWith("com.muyichen.jvm")){
// 注意这里需要给c赋值。。。
c = this.getParent().loadClass(name);
}else{
c = findClass(name);
}
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}