ClassLoader:类装载器是用来把类(class)装载进JVM的。
在java语言中,是个非常重要的概念。平时我们接触不太多。但是在以下领域我们需要了解ClassLoader的特性:
1、热部署。
2、在类加载阶段,修改字节码,增加特殊功能。(一般用的比较少)
先了解下关于ClassLoader的基本知识:
1、已经加载的类,不能重复加载,否则会抛出重复加载的异常。为了保证类型安全,必须保证在任何时刻,只要使用相同class loader对象装载同名的类,那么得到的class实例都是相同的。
为了做到这一点,就不能采用系统默认的类加载器委托规则,也就是说我们定制的类加载器在加载时,可以打破规则,自定义加载的顺序,当然这个是要以为功能的需要为目的。
类的加载又分为显式加载和隐式加载。大家使用 new 关键字创建类实例时,其实就隐式地包含了类的加载过程。对于类的显式加载来说,比较常用的是 Class.forName。其实,它们都是通过调用 ClassLoader 类的 loadClass 方法来完成类的实际加载工作的。直接调用 ClassLoader 的 loadClass 方法是另外一种不常用的显式加载类的技术。
2、java是动态加载类的,并且是逐级别的,这样的话,可以节省内存,用到什么加载什么,就是这个道理,然而系统在运行的时候并不知道我们这个应用与需要加载些什么类,那么,就采用这种逐级加载的方式。
(1)首先加载核心API,让系统最基本的运行起来
(2)加载扩展类
(3)加载用户自定义的类
3、一个Class 要想卸载,有一下要求:
1、该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例。
2、加载该类的ClassLoader已经被GC。
3、该类的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法
内部class loader:
1、bootstrap class loader --引导类加载器,它负责加载Java的核心类【java.* 】(如classpath下面的类库),不是 java.lang.ClassLoader的子类,而是由JVM自身实现的。Code . URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
2、extension classloader -扩展类加载器,它负责加载JRE的扩展目录【javax.* 】(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中JAR的类包。
3、system classloader -系统(也称为应用)类加载器,它负责加载系统或用户实现的类,在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操作系统属性所指定的JAR类包和类路径,如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。ClassLoader.getSystemClassLoader()获取。
4、User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类)
在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性。
ClassLoader加载过程:
classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入;委托机制则是先让parent(父)类加载器 (而不是super,它与parent classloader类不是继承关系)寻找,只有在parent找不到的时候才从自己的类路径中去寻找。
ClassLoader 类加载逻辑分析, 以下逻辑是除 BootstrapClassLoader 外的类加载器加载流程:
// 检查类是否已被装载过 protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class c = findLoadedClass(name); if (c == null ) { // 指定类未被装载过 try { if (parent != null ) { // 如果父类加载器不为空, 则委派给父类加载 c = parent.loadClass(name, false ); } else { // 如果父类加载器为空, 则委派给启动类加载加载 c = findBootstrapClass0(name); } } catch (ClassNotFoundException e) { // 启动类加载器或父类加载器抛出异常后, 当前类加载器将其 // 捕获, 并通过findClass方法, 由自身加载 c = findClass(name); } } }
扩展ClassLoader方法:
1、复写方法 public Class findClass(String name)
2、或者复写public byte [] loadClassData(String name)
ClassLoader的方法说明:
1、findLoadedClass:每个类加载器都维护有自己的一份已加载类名字空间,其中不能出现两个同名的类。凡是通过该类加载器加载的类,无论是直接的还是间接的,都保存在自己的名字空间中,该方法就是在该名字空间中寻找指定的类是否已存在,如果存在就返回给类的引用,否则就返回 null。这里的直接是指,存在于该类加载器的加载路径上并由该加载器完成加载,间接是指,由该类加载器把类的加载工作委托给其他类加载器完成类的实际加载。
2、getSystemClassLoader:Java2 中新增的方法。该方法返回系统使用的 ClassLoader。可以在自己定制的类加载器中通过该方法把一部分工作转交给系统类加载器去处理。
3、defineClass:该方法是 ClassLoader 中非常重要的一个方法,它接收以字节数组表示的类字节码,并把它转换成 Class 实例,该方法转换一个类的同时,会先要求装载该类的父类以及实现的接口类。
4、loadClass:加载类的入口方法,调用该方法完成类的显式加载。通过对该方法的重新实现,我们可以完全控制和管理类的加载过程。
5、resolveClass:链接一个指定的类。这是一个在某些情况下确保类可用的必要方法,详见 Java 语言规范中“执行”一章对该方法的描述。