JVM默认的三个类加载器
AppClasLoader 系统类加载器
ExtClassLoader 扩展类加载器
BootstrapClassLoader 根类加载器 (由C++实现,在控制台打印出来的是null)
他们加载的jar包所在的路径不同
父委托机制
-
Invoke
findLoadedClass(String)
to check if the class has already been loaded. -
Invoke the
loadClass
method on the parent class loader. If the parent is null the class loader built-in to the virtual machine is used, instead. -
Invoke the
findClass(String)
method to find the class.
可以看出一个类的加载是通过调用类加载器的loadClass方法来完成的,第一步检查是否已经加载过此类,第二步递归调用父加载器加载,第三步调用自己的findClass加载
自定义类加载器,打破了父委托机制
public class MyClassLoader extends ClassLoader { private String dir = "C:\Users\Administrator\Desktop"; public MyClassLoader() { } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String classPath = name.replace(".", "/"); File classFile = new File(dir, classPath + ".class"); if (!classFile.exists()) { throw new ClassNotFoundException("The class " + name + " not found under " + dir); } byte[] classBytes = loadClassBytes(classFile); if (null == classBytes || classBytes.length == 0) throw new ClassNotFoundException("load the class " + name + " failed"); return this.defineClass(name, classBytes, 0, classBytes.length); } private byte[] loadClassBytes(File classFile) { try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); FileInputStream fis = new FileInputStream(classFile)) { byte[] buffer = new byte[1024]; int len; while ((len = fis.read(buffer)) != -1) { baos.write(buffer, 0, len); } baos.flush(); return baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); return null; } } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class<?> clazz = null; // 如果是以java开头的类,就采用系统类加载器加载 if (name.startsWith("java.")) { try { ClassLoader system = ClassLoader.getSystemClassLoader(); clazz = system.loadClass(name); if (clazz != null) { if (resolve) resolveClass(clazz); return clazz; } } catch (Exception e) { // ignore } } // 打破了类加载的父委托机制 // 先自己加载,自己加载不了,就委托父加载加载 try { clazz = findClass(name); } catch (Exception e) { e.printStackTrace(); } if (clazz == null && getParent() != null) { getParent().loadClass(name); } return clazz; } } public class Test { public static void main(String[] args) throws Exception { MyClassLoader myClassLoader = new MyClassLoader(); Class aClass = myClassLoader.loadClass("com.irish.algorithm.MyObject"); System.out.println(aClass.getClassLoader()); } }
类加载器的命名空间
每一个类加载器都有自己的命名空间,由该类加载器及其所有的父加载器类构成
类的运行时包
由加载该类的命名空间+该类的可见包组成
类加载的父委托机制优缺点
优点:防止内存中出现多份同样的字节码 ,保证底层的类不会被用户写的覆盖,保证安全(即使绕过了父委托机制,与jvm底层的同名类也加载不成功)
缺点:子加载器的命名空间对父加载器不可见
热部署是采用类加载器实现的
public class User { public void add() { System.out.println("User 1.0 版本!"); } }
public class MyClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { String fileName = name.substring(name.lastIndexOf(".")+1)+".class";
//默认从当前类的目录下读取文件,传入的字符串以'/'开头的话,就是以classpath为目录 InputStream is = this.getClass().getResourceAsStream(fileName); byte [] bytes = new byte[is.available()]; is.read(bytes); return defineClass(name, bytes, 0, bytes.length); } catch (Exception e) { // TODO: handle exception throw new ClassNotFoundException(); } } }
public class Test { public static void main(String[] args) throws Exception { System.out.println("开始加载版本1.0"); loadUser(); Thread.sleep(10000); //当有类进行修改后,class文件发生了变化,重新加载该类的字节码 System.out.println("开始加载版本2.0"); loadUser(); } public static void loadUser() throws Exception { MyClassLoader classLoader = new MyClassLoader(); Class<?> findClass = classLoader.findClass("com.j0620.User"); Object obj = findClass.newInstance(); Method method = findClass.getMethod("add"); method.invoke(obj); System.out.println(obj.getClass()); System.out.println(obj.getClass().getClassLoader()); } }
项目结构: