本文主要讲解ClassLoader的含义、jvm类加载机制、ClassLoader类解读和实例。
一、ClassLoader含义 在《深入理解JAVA虚拟机中》这么解释:1 在类加载阶段,通过类的全限定名来获取描述此类的二进制字节流,让应用程序决定如何获取所需要的类。
二、java虚拟机类加载机制 类加载阶段 jvm类加载包括五个子阶段: 加载->准备->验证->解析->初始化, 其中使用Classloader去加载类的地方就在“加载”阶段。
类加载器和双亲委派模型 JVM类加载器分为:
(1) Bootstrap ClassLoader(启动类加载器)
负责加载JAVA_HOME/lib中的类和-Xbootclasspath指定的类
(2) Extension ClassLoader(扩展类加载器)
负责加载JAVA_HOME/lib/ext中的类
(3) Application ClassLoader(应用程序类加载器/系统类加载器),还可定义自定义类加载器。
负责加载用户类路径上的类,如果用户没有自定义类加载器,则AppClassLoader为默认类加载器。 同时,ClassLoader.getSystemClassLoader()返回的是该类。
使用ClassLoader.getParent()
查看父加载器, 可以看到:
自定义类加载器(不主动设置parent)的parent是AppClassLoader;
AppClassLoader的parent是ExtClassLoader;
ExtClassLoader的parent是null;
同时,需要注意上述类加载器不是类继承关系。
双亲委派模型的含义是: 1 2 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载,而是将请求委派给父加载器 ,只有当父加载器加载不到时,才会让子加载器加载。
这样就保证了在同一个JVM中基础类都只有一份,且每个类都和类加载器一起构成了一个层级关系。
然而,“规则是用来打破的”,有很多情况会破坏双亲委派模型,如: 涉及SPI(Service Provider Interface)的加载动作(JNDI、JDBC等)、热部署和热加载等操作。
ClassLoader加载类 很好的说明了双亲委派模型的动作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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; } }
三、 实例 自定义ClassLoader,需要继承ClassLoader,至少重写findClass(String)方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 /** * 自定义类加载器,只支持class文件 */ public class MyClassLoader extends ClassLoader { /** * 类路径 */ private String classPath; /** * 资源路径 */ private String resourcePath; private static MyClassLoader instance = null; public MyClassLoader(String classPath, String resourcePath) { this.classPath = classPath; this.resourcePath = resourcePath; } /** * 同步 */ public static synchronized MyClassLoader getMyClassLoader(String classPath, String resourcePath) { if (instance == null) { instance = new MyClassLoader(classPath, resourcePath); } return instance; } /** * 重写该方法,该方法在父类ClassLoader.loadClass(String)中调用 */ @Override public Class<?> findClass(String name) throws ClassNotFoundException { // 已加载,直接使用 Class<?> clz = super.findLoadedClass(name); if (clz != null) { return clz; } // 手动加载classPath中的类 return loadMyClass(name); } private Class<?> loadMyClass(String name) { // 将类名中的点改为文件分隔符,并添加.class后缀 String clzName = name.replace(".", File.separator); clzName += ".class"; // 从文件中读 String filePath = classPath + File.separator + clzName; File file = new File(filePath); if (!file.exists()) { return null; } FileInputStream in = null; ByteArrayOutputStream out = null; byte[] classBytes = null; try { in = new FileInputStream(file); out = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int size = 0; while ((size = in.read(buffer)) != -1) { out.write(buffer, 0, size); } classBytes = out.toByteArray(); } catch (IOException e) { e.printStackTrace(); } finally { closeQuietly(in); closeQuietly(out); } if (classBytes == null) { return null; } return super.defineClass(name, classBytes, 0, classBytes.length); } /** * 静默关闭 * @param obj */ private void closeQuietly(Closeable obj) { if (obj != null) { try { obj.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 重写该方法,用于获取资源 */ @Override public URL findResource(String name) { String path = resourcePath + File.separator + name; File file = new File(path); if (!file.exists()) { return null; } try { return file.toURI().toURL(); } catch (MalformedURLException e) { e.printStackTrace(); } return null; } }
使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public class Test { /** * 根目录 */ private static final String CLASSLOAD_PATH_DIR = "C:\\Users\\Administrator\\Desktop\\testclassload"; /** * 类路径 */ private static final String CLASSLOAD_PATH_CLASS = "classes"; /** * 资源路径 */ private static final String CLASSLOAD_PATH_RESOURCES = "\\resources"; public static void main(String[] args) throws Exception { String classPath = CLASSLOAD_PATH_DIR + File.separator + CLASSLOAD_PATH_CLASS; String resourcePath = CLASSLOAD_PATH_DIR + File.separator + CLASSLOAD_PATH_RESOURCES; MyClassLoader myClassLoader = MyClassLoader.getMyClassLoader(classPath, resourcePath); // 待加载类,不在当前工程中 String bookClzName = "com.test.Book"; Class<?> bookClz = myClassLoader.loadClass(bookClzName); System.out.println("bookClz's classLoader is : " + bookClz.getClassLoader().getClass().getName()); // 加载后new出实例 Constructor constructor = bookClz.getConstructor(new Class[] {String.class, double.class}); Method method = bookClz.getMethod("puzzleTime"); if (!method.isAccessible()) { method.setAccessible(true); } method.invoke(constructor.newInstance(new Object[] {"Book0", 20})); } } 运行结果: bookClz's classLoader is : com.test.MyClassLoader OK, you found me! /** * 待加载类 */ public class Book { private String bookName; private double price; public Book(String bookName, double price) { this.bookName = bookName; this.price = price; } public void puzzleTime() { System.out.println("OK, you found me!"); } }