先看例子:
public class ClassLoaderTest{ public static void main(String[] args) { ClassLoader cl = ClassLoaderTest.class.getClassLoader(); System.out.println(cl); System.out.println(cl.getParent()); System.out.println(cl.getParent().getParent()); /** * List是rt.jar包下 */ System.out.println(List.class.getClassLoader()); } }
输出结果:
[email protected]
[email protected]
null
null
系统默认三个类加载器
BootStrap(根加载器):java核心库(rt.jar),C/C++写,故而打印出 null
ExtClassLoader(扩展加载器):ext包下
AppClassLoader:classpath下
类加载器的委托机制:
当Java虚拟机要加载第一个类的时候,到底派出哪个类加载器去加载呢?
(1). 首先当前线程的类加载器去加载线程中的第一个类(当前线程的类加载器:Thread类中有一个get/setContextClassLoader(ClassLoader cl);方法,可以获取/指定本线程中的类加载器)
(2). 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B
(3). 还可以直接调用ClassLoader.loadClass(String className)方法来指定某个类加载器去加载某个类
每个类加载器加载类时,又先委托给其上级类加载器当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则会抛出ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild()方法。例如:如上图所示: MyClassLoader->AppClassLoader->ExtClassLoader->BootStrap.自定定义的MyClassLoader1首先会先委托给AppClassLoader,AppClassLoader会委托给ExtClassLoader,ExtClassLoader会委托给BootStrap,这时候BootStrap就去加载,如果加载成功,就结束了。如果加载失败,就交给ExtClassLoader去加载,如果ExtClassLoader加载成功了,就结束了,如果加载失败就交给AppClassLoader加载,如果加载成功,就结束了,如果加载失败,就交给自定义的MyClassLoader1类加载器加载,如果加载失败,就报ClassNotFoundException异常,结束。
在看一个例子 :
public class MyClassLoader extends ClassLoader{ protected MyClassLoader(ClassLoader parent) { super(parent); } protected MyClassLoader() { super(); } } public class ClassLoaderTest2 { public static void main(String[] args) { MyClassLoader ml1 = new MyClassLoader(); System.out.println(ml1.getParent()); MyClassLoader ml2 = new MyClassLoader(Thread.currentThread().getContextClassLoader().getParent()); System.out.println(ml2.getParent()); } }
打印结果:
[email protected]
[email protected]
说明自定义类加载器的父加载器默认是 :AppClassLoader
自定义的类加载器必须继承抽象类ClassLoader
一般只需 重写findClass方法,其实他内部还有一个loadClass方法和defineClass方法;
看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 { //如果有parent,就让parent加载,如果找不到该类, //抛ClassNotFoundException ,让子加载器加载,直到加载成功结束 if (parent != null) { c = parent.loadClass(name, false); } else { //如果自定义加载器parent为null,直接用根加载器加载 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) { 解析class文件 resolveClass(c); } return c; } } //该方法直接抛出异常,就是为了重写,所以自定义加载器只需要重写findClass,如果重写 // load方法,还需要重新写容器的委托逻辑, 没有必要 protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
defineClass这个方法很简单就是将class文件的字节数组编程一个class对象,这个方法肯定不能重写,内部实现是在C/C++代码中实现的
自定义类加载器:
public class MyClassLoader extends ClassLoader{ private String base_dir; protected MyClassLoader(ClassLoader parent) { super(parent); } protected MyClassLoader() { super(); } public MyClassLoader(ClassLoader parent,String base_dir) { super(parent); this.base_dir = base_dir; } public MyClassLoader(String base_dir) { super(); this.base_dir = base_dir; } //重写findClass @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String path = this.base_dir+"\\"+name+".class"; byte[] b = new byte[0]; try { b = toByteArray(path); } catch (IOException e) { e.printStackTrace(); } if(b.length == 0){ return null; } //定义class信息,把class文件字节码信息组装成jvm的class信息 return defineClass(null,b,0,b.length); } private byte[] toByteArray(String filename) throws IOException { File f = new File(filename); if (!f.exists()) { throw new FileNotFoundException(filename); } ByteArrayOutputStream bos = new ByteArrayOutputStream((int) f.length()); BufferedInputStream in = null; try { in = new BufferedInputStream(new FileInputStream(f)); int buf_size = 1024; byte[] buffer = new byte[buf_size]; int len = 0; while (-1 != (len = in.read(buffer, 0, buf_size))) { bos.write(buffer, 0, len); } return bos.toByteArray(); } catch (IOException e) { e.printStackTrace(); throw e; } finally { try { in.close(); } catch (IOException e) { e.printStackTrace(); } bos.close(); } } } public class ClassLoaderTest3 { public static void main(String[] args) { MyClassLoader ml = new MyClassLoader("E:\\workspace\\mytest\\temp"); try { Class helloClass = ml.loadClass("Hello"); Object hello = helloClass.newInstance(); System.out.println(hello.getClass().getClassLoader()); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
打印结果:
[email protected]
这里要声明:很容易打印出 AppClassLoader,这是由于你本地idea创建的Hello.class文件可能存在 项目class目录下,删除,从其他目录引入 即可;
现在来测试下自定义的类加载器:
public class Hello { public String sayHello(){ return "hello classLoader"; } } public class ClassLoaderTest3 { public static void main(String[] args) { //指定根路径,可以根据实际情况引入 MyClassLoader ml = new MyClassLoader(ClassLoader.getSystemClassLoader().getParent(),"E:\\workspace\\mytest\\temp"); try { //加载 Class helloClass = ml.loadClass("Hello"); //初始化 Object hello = helloClass.newInstance(); //调用 加载类的方法 Object info = helloClass.getMethod("sayHello").invoke(hello); System.out.println(info); System.out.println(hello.getClass().getClassLoader()); } catch (Exception e) { e.printStackTrace(); } } }
打印结果:
hello classLoader
[email protected]