双亲委托机制介绍
双亲委托机制有时也称父委托机制,当一个类调用loadClass之后并不会直接将其加载,而是先交给当前类加载器的父加载器尝试加载,知道最顶层的父加载器,然后再一次向下进行加载。
loadClass方法源码
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
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(name,false).
- 如果不存在父加载器,则调用根加载器对该类进行加载。
- 如果所有加载器都没有加载成功,则尝试调用当前类加载器的findClass进行加载,就是我们自定义加载器需要重写的方法。
- 加载成功后做一些性能数据的统计。
- 由于resolve为false,所以不会进行连接阶段的继续执行,即是通过类加载器加载的类并不会导致类的初始化。
破坏双亲委托机制
重写的loadClass方法
@Override
protected Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) {
Class<?> klass = findLoadedClass(name);
if(klass==null) {
if(name.startsWith("java.") || name.startsWith("javax")) {
try {
klass = getSystemClassLoader().loadClass(name);
} catch (Exception e) {
// TODO: handle exception
}
}else {
try {
klass = this.findClass(name);
} catch (Exception e) {
// TODO: handle exception
}
if(klass==null) {
if(getParent()!=null) {
klass = getParent().loadClass(name);
}else {
klass = getSystemClassLoader().loadClass(name);
}
}
}
}
if(klass==null) {
throw new ClassNotFoundException("the class "+name+" not found");
}
if(resolve) {
resolveClass(klass);
}
return klass;
}
1.根据类的全路径名进行加锁,确保每一个类在多线程的情况下只能被加载一次。
2.先到加载类的缓存中查看是否已经被加载,如果已经加载则直接返回。
3.若缓存中没有则对类进行首次加载。
4.如果类的全路径名以java和Javax开头的则直接交给父加载器进行加载。否则就尝试用自定义类加载器进行加载。
5.如果自定义加载器没有完成加载,则委托父加载器进行加载或者系统加载器进行加载。
6.如果都加载失败后则抛出ClassNotFoundException异常。
类加载器的命名空间
每个类加载器实例都有自己的命名空间,命名空间由该加载器及其父加载器所构成,因此在每个类加载器中同一个class是独一无二的。但是不同的类加载器,或者同一个类的不同实例加载同一个class则会在堆内存和方法区产生多个class的对象。
不同类加载器加载同一个class
public static void main(String[] args) throws ClassNotFoundException,
InstantiationException,
IllegalAccessException,
NoSuchMethodException,
SecurityException,
IllegalArgumentException,
InvocationTargetException {
MyClassLoader myclassLoader =
new MyClassLoader("F:\\eclipse-jee-2018-12-R-win32-x86_64\\ClassLoader",null);
BorkerDelegateClassLoader brclass =
new BorkerDelegateClassLoader("F:\\eclipse-jee-2018-12-R-win32-x86_64\\ClassLoader",null);
Class<?> aclass= myclassLoader.loadClass("com.pys.classloader.MyHello");
System.out.println("agetClassLoader "+aclass.getClassLoader());
Class<?> bclass= brclass.loadClass("com.pys.classloader.MyHello");
System.out.println("bgetClassLoader "+bclass.getClassLoader());
System.out.println(bclass==aclass);
}
输出
agetClassLoader my ClassLoader 加载器
bgetClassLoader my BorkerDelegateClassLoader 加载器
false
同一个加载器的不同实例加载同一个class
public static void main(String[] args) throws ClassNotFoundException,
InstantiationException,
IllegalAccessException,
NoSuchMethodException,
SecurityException,
IllegalArgumentException,
InvocationTargetException {
MyClassLoader myclassLoader =
new MyClassLoader("F:\\eclipse-jee-2018-12-R-win32-x86_64\\ClassLoader",null);
MyClassLoaderbrclass =
new MyClassLoader("F:\\eclipse-jee-2018-12-R-win32-x86_64\\ClassLoader",null);
Class<?> aclass= myclassLoader.loadClass("com.pys.classloader.MyHello");
System.out.println("agetClassLoader "+aclass.getClassLoader());
Class<?> bclass= brclass.loadClass("com.pys.classloader.MyHello");
System.out.println("bgetClassLoader "+bclass.getClassLoader());
System.out.println(bclass==aclass);
}
输出
agetClassLoader my ClassLoader 加载器
bgetClassLoader my ClassLoader 加载器
false
运行时包
包的作用是为了组织类,防止不同包下同样名称的class引起冲突,还能起到封装的作用,包名和类名构成了类的全限定名称。在JVM运行时class会有一个运行时包,是由类加载器的命名空间和类的全限定名称共同组成。
初始类加载器
JVM规定了不同的运行时包下的类彼此之间是不可以进行访问的,但是我们在实际开发中却时可以使用java.lang.String,ava.Utils.List等类,这些类都是由根加载器进行加载的。
因为JVM为每一个类加载器维护了一个列表,该列表中记录了该类加载器作为初始类加载器的所有class,在加载一个类时JVM也是使用这些列表来判断该类是否加载过,是否需要首次加载,JVM类的加载过程中所有参与的类加载器,即使没有亲自加载过该类,也都会被标识为该类的初始类加载器。所以当我们在开发中使用String并由自定义的类加载器进行加载时,依次又经过了系统类加载器-》扩展类加载器-》根加载器,这些类加载器都是String的初始类加载器,所以这就是在开发中创建的类和String是由不同的类加载器完成加载,但是却可以正常访问的原因。
类的卸载
以下三个条件都满足时类被卸载:
1.该类的所有实例都被回收。
2.加载该类的ClassLoader实例被回收。
3.该类的class实例没有在其他地方被引用。