Java类加载器(英语:Java Classloader)是Java运行时环境(Java Runtime Environment)的一部分,负责动态加载Java类到Java虚拟机的内存空间中。类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。学习类加载器时,掌握Java的委派概念很重要。

每个Java类必须由某个类加载器装入到内存。Java程序可以利用外部库(即由其他作者编写的软件库)。

JVM中有3个默认的类加载器:

  1. 引导(Bootstrap)类加载器。由原生代码(如C语言)编写,不继承自java.lang.ClassLoader。负责加载核心Java库,存储在<JAVA_HOME>/jre/lib目录中。
  2. 扩展(Extensions)类加载器。用来在<JAVA_HOME>/jre/lib/ext,或java.ext.dirs中指明的目录中加载 Java的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。该类由sun.misc.LauncherExtClassLoader实现。
  3. Apps类加载器(也称系统类加载器)。根据 Java应用程序的类路径(java.class.path或CLASSPATH环境变量)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。该类由sun.misc.Launcher$AppClassLoader实现。
  4. 每个类装载器有一个父装载器(parent class loader)。

 类的唯一性

对于任意一个类,都需要由加载它的类加载器类的全限定名一同确定其在Java虚拟机中的唯一性

换句话说就是,比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。

这里所指的“相等”,包括Class对象的equals()方法,isAssignableFrom()方法、isInstance()方法的返回结果,也包括使用instanceof关键字做对象所属关系判定等情况。

类加载器的命名空间

每个类加载器有自己的命名空间,命名空间由所有以此加载器为初始加装载器的类组成。

不同类加载器的命名空间关系:

1、同一个命名空间内的类是相互可见的,即可以互相访问。

2、父加载器的命名空间对子加载器可见。

3、子加载器的命名空间对父加载器不可见。

4、如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见。

启动类加载器(Bootstrap ClassLoader):这个类加载器负责将存放在\lib目录中,或者被-Xbootclasspath虚拟机参数指定的路径中,并且是虚拟机识别的类库加载到虚拟机内存中。(仅按照文件名识别,如rt.jar,名称不符合的类库即使放在lib目录中也不会被加载。)

扩展类加载器(Extension ClassLoader):这个类加载器由sun.misc.Launcher&ExtClassLoader实现,它负责加载\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher$ApplicationClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

类加载器体系可以通过使用不同的类加载器加载可信包(trusted packages)和不可信包(untrusted packages)从而保护可信包的安全。尽管你可以对同一包中的类型制定访问控制,但这种控制只有在被同一个加载器加载的前提下才起作用。

什么是类加载器

类加载器是一个用来加载类文件的类。Java源代码通过javac编译器编译成类文件。然后JVM来执行类文件中的字节码来执行程序。类加载器负责加载文件系统、网络或其他来源的类文件。有三种默认使用的类加载器:Bootstrap类加载器、Extension类加载器和System类加载器(或者叫作Application类加载器)。每种类加载器都有设定好从哪里加载类。

  • Bootstrap类加载器负责加载rt.jar中的JDK类文件,它是所有类加载器的父加载器。Bootstrap类加载器没有任何父类加载器,如果你调用String.class.getClassLoader(),会返回null,任何基于此的代码会抛出NUllPointerException异常。Bootstrap加载器被称为初始类加载器。
  • 而Extension将加载类的请求先委托给它的父加载器,也就是Bootstrap,如果没有成功加载的话,再从jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下加载类。Extension加载器由sun.misc.Launcher$ExtClassLoader实现。
  • 第三种默认的加载器就是System类加载器(又叫作Application类加载器)了。它负责从classpath环境变量中加载某些应用相关的类,classpath环境变量通常由-classpath或-cp命令行选项来定义,或者是JAR中的Manifest的classpath属性。Application类加载器是Extension类加载器的子加载器。通过sun.misc.Launcher$AppClassLoader实现。

 总结一下,下面是三种类加载器加载类文件的地方:

1) Bootstrap类加载器 – JRE/lib/rt.jar

2) Extension类加载器 – JRE/lib/ext或者java.ext.dirs指向的目录

3) Application类加载器 – CLASSPATH环境变量, 由-classpath或-cp选项定义,或者是JAR中的Manifest的classpath属性定义.

类加载器的工作原理

加载器的工作原理基

三个机制:委托、可见性和单一性。这一节,我们来详细看看这些规则,并用一个实例来理解工作原理。下面显示的是类加载器使用委托机制的工作原理。

委托机制

当一个类加载和初始化的时候,类仅在有需要加载的时候被加载。假设你有一个应用需要的类叫作Abc.class,首先加载这个类的请求由Application类加载器委托给它的父类加载器Extension类加载器,然后再委托给Bootstrap类加载器。Bootstrap类加载器会先看看rt.jar中有没有这个类,因为并没有这个类,所以这个请求由回到Extension类加载器,它会查看jre/lib/ext目录下有没有这个类,如果这个类被Extension类加载器找到了,那么它将被加载,而Application类加载器不会加载这个类;而如果这个类没有被Extension类加载器找到,那么再由Application类加载器从classpath中寻找。记住classpath定义的是类文件的加载目录,而PATH是定义的是可执行程序如javac,java等的执行路径。

可见性机制

根据可见性机制,子类加载器可以看到父类加载器加载的类,而反之则不行。所以下面的例子中,当Abc.class已经被Application类加载器加载过了,然后如果想要使用Extension类加载器加载这个类,将会抛出java.lang.ClassNotFoundException异常。

单一性机制

根据这个机制,父加载器加载过的类不能被子加载器加载第二次。虽然重写违反委托和单一性机制的类加载器是可能的,但这样做并不可取。你写自己的类加载器的时候应该严格遵守这三条机制。

如何显式的加载类

Java提供了显式加载类的API:Class.forName(classname)和Class.forName(classname, initialized, classloader)。就像上面的例子中,你可以指定类加载器的名称以及要加载的类的名称。类的加载是通过调用java.lang.ClassLoader的loadClass()方法,而loadClass()方法则调用了findClass()方法来定位相应类的字节码。在这个例子中Extension类加载器使用了java.net.URLClassLoader,它从JAR和目录中进行查找类文件,所有以”/”结尾的查找路径被认为是目录。如果findClass()没有找到那么它会抛出java.lang.ClassNotFoundException异常,而如果找到的话则会调用defineClass()将字节码转化成类实例,然后返回。

什么地方使用类加载器

类加载器是个很强大的概念,很多地方被运用。最经典的例子就是AppletClassLoader,它被用来加载Applet使用的类,而Applets大部分是在网上使用,而非本地的操作系统使用。使用不同的类加载器,你可以从不同的源地址加载同一个类,它们被视为不同的类。J2EE使用多个类加载器加载不同地方的类,例如WAR文件由Web-app类加载器加载,而EJB-JAR中的类由另外的类加载器加载。有些服务器也支持热部署,这也由类加载器实现。你也可以使用类加载器来加载数据库或者其他持久层的数据。

以上是关于类加载器的工作原理。我们已经知道了委托、可见性以及单一性原理,这些对于调试类加载器相关问题时至关重要。这些对于Java程序员和架构师来说都是必不可少的知识。