java中的类加载器:bootstrap,ExtClassLoader,AppClassLoader
bootstrap 使用c++编写
sun.misc.Launcher.ExtClassLoader
sun.misc.Launcher.AppClassLoader
继承关系:
ClassLoader -> SecureClassLoader -> URLClassLoader -> sun.misc.Launcher$AppClassLoader
ClassLoader -> SecureClassLoader -> URLClassLoader -> sun.misc.Launcher$ExtClassLoader
下面一段话翻译自Javassist Tutorial:
在java中,多个class loader可以共存,每一个class loader创建自己的命名空间。不同的类加载器可以加载同名的但内容不同的class文件。加载的2个类被认为是不同的。这个特性使我们可以在单个jvm内部运行不同的程序,即使这些程序包含同名的class。
(注意:jvm不允许动态加载类。一旦一个class loader加载了某个类,该class loader不能在运行期内加载修改过的类。所以在jvm加载这个类后,你不能修改它的定义了。JPDA (Java Platform Debugger Architecture)提供了有限的重载一个类的能力。)
如果同一个class文件被两个不同的class loader加载了,jvm会使用相同的名字和定义创建两个不同的class对象。这两个class被认为是不同的。因为这两个类是不同的,一个类的实例不能赋值给另一个类的变量。 两个类之间的转换操作会失败,并抛ClassCastException异常。
例如:下面的代码片段会抛出异常。
MyClassLoader myLoader = new MyClassLoader();
Class clazz = myLoader.loadClass("Box");
Object obj = clazz.newInstance();
Box b = (Box)obj; // this always throws ClassCastException.
上面的话,用一幅图表示:
我们知道bootstrap是ExtClassLoader的父亲,ExtClassLoader是AppClassLoader的父亲,bootstrap负责rt.jar,ExtClassLoader负责ext目录中的jar,而AppClassLoader加载我们自己写的类。加载类的时候呢,使用的是双亲委派机制,先给父亲加载,在父亲的管辖范围内呢,父亲就会加载,反之则自己加载这个类。
但是,我们自己定义的类加载器,可以不使用双亲委派机制。
以下是一个自定义类加载器,from(https://yq.aliyun.com/articles/55616):
class MyClassLoader extends ClassLoader { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream is = getClass().getResourceAsStream(fileName); if (is == null) { return super.loadClass(name); } byte[] b = new byte[is.available()]; is.read(b); return defineClass(name, b, 0, b.length); } catch (Exception e) { throw new ClassNotFoundException(name); } } }
线程内部有一个上下文类加载器:
public class Thread implements Runnable { /* The context ClassLoader for this thread */ private ClassLoader contextClassLoader; }
改变当前线程的contextClassLoader后,从当前线程派生出的子线程则使用修改后的contextClassLoader。
从类的Class对象可以获得加载该类的ClassLoader,从线程能获取该线程的contextClassLoader。
public class ClassLoader_Test { public static void main(String[] args) throws ClassNotFoundException { System.out.println(ClassLoader_Test.class.getClassLoader()); System.out.println(Thread.currentThread().getContextClassLoader()); } }
平常,我们习惯用 Class.forName("xxx") 获取类的Class对象,其底层是调用ClassLoader.loadClass方法。