Java classLoader

avatar

定义

是Java运行时环境(Java Runtime Environment)的一个部件,负责动态加载Java类到Java虚拟机的内存空间中。类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。对学习类加载器而言,掌握Java的委派概念是很重要的。 每个Java类必须由某个类加载器装入到内存。Java程序可以通过类加载器来利用外部库 (维基百科)

类加载机制

把class文件的字节码内容加载到内存,对其进行校验,解析等操作,并将这些静态数据转换成方法区的运行时数据,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类的访问入口。 image.png 类的生命周期

  1. 加载
  2. 链接
  3. 初始化

类加载器

image.png

  • Bootstrap ClassLoader 

最顶层加载器,引导作用,加载Java核心类库如%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。属于JVM,只加载特定的类,其他的类哪怕手动放到其加载空间里也不会被加载。

  • Extention ClassLoader

扩展类加载器,由sun.misc.Launcher.ExtClassLoader类实现,加载如%JRE_HOME%\lib\ext目录下的jar包和class文件

  • App ClassLoader

也称为SystemClassLoader ,由sun.misc.Launcher.AppClassLoader实现,加载当前Java应用classpath的所有类。如果应用自己不定义类加载器,这个就是应用的默认加载器。

  • Custom ClassLoader

自定义的ClassLoader,通过继承java.lang.ClassLoader类实现

JVM启动时,各ClassLoader的初始化顺序为从上到下。但加载顺序是从下到上。

特点

  1. 类加载器有自己特定的加载范围。
  2. 类的唯一性由类跟加载它的类加载器共同决定,也就是同一个class被不同类加载器加载,它在JVM的表现是不同的类,虽然他们是同一class文件。
  3. 通常情况下,我们使用JVM默认的三种加载器配合使用,即可完成基础加载,加载也是按需加载。加载过程基于双亲委托的机制,即优先由自己的父加载器去处理类加载。

双亲委托(parents delegate)

一个类加载器在接待类加载请求,首先不是自己亲自去加载类。而是委托给父加载器,如果父加载器可以完成这个类加载请求,就成功返回。只有父加载器完成不了,自己才去加载。 image.png

特性

  • 委托:子加载器委托给父加载器加载
  • 可见效:子加载器可以访问父加载器的加载范围,而反之则不行。
  • 唯一性:保证每个类只被加载一次,比如加载Object.class,String.class这样的类都会委托到BootstrapClassLoader。保证其只被加载一起

优点

  1. 避免重复加载,父加载器先加载该类,子加载器没必要再加载,而是直接返回。
  2. 安全性,优先加载jdk的类,而不必担心某个核心类被篡改带来的安全性问题。比如核心类string会一直被bootstrapClassloader加载,自定义的String是无法替代的!
package java.lang;

public class SensoroClass {

    public static void main(String[] args) {
        System.out.println("SensoroClassLoader: " + SensoroClass.class.getClassLoader());
    }

}


报错 image.png

ClassLoader类源码简析

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            //找不到或者已经加载则返回null
            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) {
                //Links the specified class
                resolveClass(c);
            }
            return c;
        }
    }

应用场景

  • 资源隔离

    有时候需要加载不同版本的jar,这时可以自定义类加载器

  • 热部署

    运行时想要更新class文件,达到热更新。那就少不了类装载器。因为类装载器不能再次加载一个已经加载的类,所以可以通过再来一个类加载器把再次加载一个加载好的类。这样还不需要重启应用程序。

  • 代码保护

假如字节码文件有加密的需求,别人反编译也无法看到核心信息。我们就可以在自定义加载器里实现对它的解密,再加载进来进行使用。