我们都知道Java的平台无关性是其一大特点,所谓平台无关性是指Java的源码会被编译成Class字节码文件,其字节码文件可以被不同平台上的JVM所解析然后执行,从而实现跨平台性。那么这一过程是具体是如何实现的呢?
类加载器
类加载器(ClassLoader),顾名思义就是专门用来加载类的,其作用就是将编译好的Class文件加载到JVM中。在Java中默认提供类三种类加载器:
- Bootstrap ClassLoader 这个加载器主要负责加载Java中核心包,例如java.lang.*
- ExtClassLoader 这个加载器主要负责加载Java中的扩展包,例如javax
- AppClassLoader 这个加载器主要负责加载应用程序主程序类
同时,也可以自定义ClassLoader。通过类加载器,可以将我们的Class字节码文件转化为相应的二进制字节流到JVM中。那么此时就会出现一个问题,一个Class字节码文件该由哪个类加载器来执行呢?
双亲委派机制
双亲委派机制就是为了解决多个加载器重复解析同一个字节码文件,其机制如下图:
当有一个出现Class字节码文件时,首先交由自定义加载器看是否加载,如果加载了则不继续提交给父加载器,如果不加载则提交给父加载器AppClassLoader。同样如果AppClassLoader不加载则继续提交给其父加载器ExtClassLoader,如果ExtClassLoader还是不加载则最终提交给Bootstrap ClassLoader。此过程自底向上。
然后Bootstrap ClassLoader开始从尝试加载此字节码文件,如果它表示无法加载,则向下转交给子加载器。以此类推,如果最终自定义类加载器也无法加载此字节码文件则抛出异常。此过程自顶向下。
此流程即为双亲委派机制,通过这个机制可以有效的避免对字节码文件的重复加载。
类加载机制
类加载过程主要分为以下三个过程:
之前,通过类加载器实现了第一步,加载过程。
接着,用户想要拿到JVM中的Class对象,有两种方法,一种为forName()方法,一种为loadClass()方法,其过程完全不同。
forName()的实现过程:
Class test = Class.forName("Test");
直接通过Class的静态方法可以获取想要的对象,此过程会自动实现链接和初始化阶段。
loadClass()的实现过程:
ClassLoader cl = Test.class.getClassLoader();
Class test = cl.loadClass("Test");
首先获取处理该字节码的类加载器,然后通过类加载器生成用户想要的对象,和forName()方法不同的是此方法只完成了加载阶段,并没有实现链接和初始化阶段。那么这种方法有什么应用场景呢?在Spring中,可以loadClass()方法实现一种加载方式—懒加载。只将类加载到JVM中,只有当该类真正用到的时候才会被检查和初始化,这样可以大大的提高运行的效率。