加载器名 | 方法名 | 作用 |
---|---|---|
Bootstrap class loader | 无 | 虚拟机的内置类加载器,底层是用C++实现的,没有父加载器。主要加载系统环境的一些jar包和.class文件。C/C++语言编写,是虚拟机的一部分,无法在Java代码中直接获取它的引用。可以通过 System.getProperty(“sun.boot.class.path”)获取其加载路径下的文件 |
Platform class loader (也称为ExtClassLoader) | getPlatformClassLoader() | 平台类加载器,负责加载JDK中一些特殊的模块。主要加载java.ext.dirs下的.class文件 |
System class loader (也称为AppClassLoader) | getSystemClassLoader() | 系统类加载器,负责加载用户类路径上所指定的类库。主要加载java.class.path下的.class文件,是面向用户编写类的类加载器,即自己写的类或者引入的第三方库通常由此加载器加载 |
方法名 | 作用 |
---|---|
protected ClassLoader(String name, ClassLoader parent) | 创建指定名称name的新类加载器,并使用指定的父类加载器parent进行委派 |
public String getName() | 返回此类加载器的名称,如果此类加载器未命名,则返回null |
public Class loadClass(String name, boolean resolve) | 加载具有指定类名称的类,resolve为true表示解析类引用。loadClass方法会先调用getClassLoadingLock方法获取锁,再调用findLoadedClass方法检查类是否已加载,如果未加载,则往父加载器一直递归调用loadClass加载该类,如果父加载器也加载不了该类,才调用findClass方法获取Class对象,而findClass是虚拟方法由子类实现。其实现使用defineClass(String name, byte[] b, int off, int len)方法可以将class文件的字节数组转为Class对象,最后使用resolveClass方法进行类的链接。而由于获取该字节数组的方法是很多样的,所以类加载的方式也非常多样,如本地加载、网络加载、压缩包中加载、自己构建Class文件 |
protected Object getClassLoadingLock(String className) | 返回类加载操作的锁对象。 如果此ClassLoader对象注册为支持并行,则该方法返回与指定类名 className关联的专用对象。否则该方法将返回此ClassLoader对象,即同一时间一个ClassLoader只能加载一个类 |
protected final Class findLoadedClass(String name) | 如果Java虚拟机已将此加载程序记录为具有给定全限定类名称的类的初始加载程序,则返回具有给定全限定类名称的类。否则返回 null |
protected Class findClass(String name) | 查找具有指定全限定类名的类。这个方法是空方法应该被子类重写,并且被调用在检查请求类的父类加载器之后,loadClass方法将调用这个方法 |
protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) | 将字节数组转换为具有给定保护域ProtectionDomain的类Class的实例。 如果指定的name以“java.”开头,它只能由getPlatformClassLoader()获取到的平台类加载器或其祖先定义(define),否则将抛出SecurityException。如果name不是null,则它必须等于字节数组b指定的类的全限定类名称,否则将抛出NoClassDefFoundError |
protected final Class<?> defineClass(String name, java.nio.ByteBuffer b, ProtectionDomain protectionDomain) | 将字节缓冲区ByteBuffer转换为具有给定保护域ProtectionDomain的类Class的实例。其余和上面一样 |
protected final void resolveClass(Class c) | 链接指定的类。类加载器可能会使用此方法链接类。如果类c已经被链接,那么这个方法直接返回。 否则,将按照Java语言规范执行一章中的描述链接该类 |
通过上面我们可以知道类加载流程是
项目结构,out是编译出的class文件目录,由于Test就在src目录下,没有包名,则其全限定类名为Test
public class Test extends ClassLoader { public static void main(String[] args) throws Exception { while(true) { try { Test test = new Test(); // 编译后的class文件位置 ./表示代码根目录 String classFile = "./out/production/Java_hot_replace/Test.class"; FileInputStream fis = new FileInputStream(classFile); byte[] bytes = new byte[1024*10]; int len = fis.read(bytes); //将字节数组转为Class类对象 Test为全限定类名 Class clazz = test.defineClass("Test", bytes, 0 ,len); //使用反射根据新的Class对象创建新对象,并执行其printStr方法 Object object = clazz.newInstance(); Method m = object.getClass().getMethod("printStr", new Class[] {}); m.invoke(object, new Object[] {}); Thread.sleep(2000); } catch(Exception e) { e.printStackTrace(); try { Thread.sleep(2000); } catch(InterruptedException ex) { } } } } public void printStr() { System.out.println("A"); } }
启动后修改代码,然后点重新编译
可以看到代码被热替换了
public class Test extends ClassLoader { public static void main(String[] args) throws Exception { MyClassLoader classLoader = new MyClassLoader(); Class classLoaded = classLoader.loadClass("MyClass"); classLoaded = null; classLoader = null; System.gc(); } }