什么是类加载机制

编译器将java源码编译为字节码后,虚拟机将字节码读入内存进行解析、运行的过程,称之为java虚拟机的类加载机制。
类加载机制中,使用类加载器完成类的加载过程。
类的生命周期在上文中详细描述过,这里不赘述。详见Java类加载过程详解

什么是类加载器?

通过一个类全限定名称来获取其二进制文件(.class)流的工具,被称为类加载器(classloader)

类加载器的分类

java支持如下四种类加载器
类加载器

  1. 启动类/引导类加载器 Bootstrap ClassLoader
    由C/C++实现,嵌套在JVM内部,java程序无法直接操作此类。通过它来加载java核心类库。如JAVA_HOME/jre/lib/rt.jar、resources.jar、sun.boot.class.path路径下的包,为jvm运行提供所需要的包。
    它并非继承自java.lang.ClassLoader,没有父类加载器。它负责加载扩展类加载器应用程序类加载器,并成为其父类加载器。
    处于安全考虑,启动类只加载包名为java|javax|sun开头的类。

  2. 扩展类加载器 Extension ClassLoader
    由java语言编写,由sun.misc.Launcher$ExtClassLoader实现,可以用java程序操作此类加载器。
    它继承自java.lang.ClassLoader,父类加载器为启动类加载器
    从系统属性:java.ext.dirs目录中加载类库,
    从JDK安装目录:jre/lib/ext目录下加载类库。
    将包放在上述目录下,就会自动被加载。

  3. 应用程序类加载器 Application Classloader
    由Java语言编写,由 sun.misc.Launcher$AppClassLoader实现。
    它继承自java.lang.ClassLoader,父类加载器为启动类加载器
    它负责加载环境变量classpath或者系统属性java.class.path指定路径下的类库
    它是程序中默认的类加载器,我们Java程序中的类,都是由它加载完成的。可以通过ClassLoader#getSystemClassLoader()获取并操作这个加载器。

  4. 自定义加载器

  • 上述三种类加载器无法满足开发需求时,用户可以自定义类加载器
  • 自定义类加载器,需要继承抽象类java.lang.ClassLoader,如果期望打破双亲委派机制,则重写loadClass方法;如果不想打破双亲委派机制,则重写findClass方法即可。

ClassLoader源码

ClassLoader的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public abstract class ClassLoader {

private static native void registerNatives();
static {
registerNatives();
}

// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
private final ClassLoader parent;

protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

// ...

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

通过如下方式可以验证上述所说的类加载器的加载路径:

1
2
3
System.out.println("boot:" + System.getProperty("sun.boot.class.path"));
System.out.println("ext:" + System.getProperty("java.ext.dirs"));
System.out.println("app:" + System.getProperty("java.class.path"));

如何自定义类加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.io.IOException;
import java.io.InputStream;

public class ConsumerClassLoaderDemo extends ClassLoader {

public static void main(String[] args) throws Exception {

ClassLoader myClassLoader = new ConsumerClassLoader();
Object obj = myClassLoader.loadClass("com.lbs0912.java.demo.ConsumerClassLoaderDemo").newInstance();
ClassLoader classLoader = obj.getClass().getClassLoader();
// BootStrapClassLoader在Java中不存在的,因此会是null
while (null != classLoader) {
System.out.println(classLoader);
classLoader = classLoader.getParent();
}
}
}

class ConsumerClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String classFile = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream in = getClass().getResourceAsStream(classFile);
if (null == in) {
return super.loadClass(name);
}
byte[] bytes = new byte[in.available()];
in.read(bytes);
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
}

控制台输出如下:

1
2
3
4
com.lbs0912.java.demo.ConsumerClassLoader@266474c2
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@63947c6b

java9 类加载机制的改动

java 9中有如下三种类加载器:

  • 启动类加载器:
  • 平台类加载器:
  • 应用类加载器:

如何获取类加载器

ClassLoader是抽象类,除启动类加载器外,其余类加载器均继承自ClassLoader

1
2
3
4
5
6
7
8
// 方式一:获取当前类的 ClassLoader
clazz.getClassLoader()
// 方式二:获取当前线程上下文的 ClassLoader
Thread.currentThread().getContextClassLoader()
// 方式三:获取系统的 ClassLoader
ClassLoader.getSystemClassLoader()
// 方式四:获取调用者的 ClassLoader
DriverManager.getCallerClassLoader()

类加载机制的特点

类加载机制通过ClassLoader完成类的加载,java类的确定由类和加载它的类加载器共同决定。它有如下三个特点:

  • 双亲委派:
    将请求交给父类处理的任务委派方式
  • 负责依赖:
    如果一个类加载器在加载某个类时,发现此类依赖其他几个类或接口,也会去尝试加载这些依赖项
  • 缓存加载:
    为提升加载效率,一旦某个类被类加载器加载,会缓存这个加载结果。 【类加载到方法区和即时编译存放的异同?】

疑问

  1. 反向委派的过程只能使用线程上下文类加载器么?
  2. 为什么启动类加载器无法加载SPI的实现类?

引用

1. jvm类加载器,类加载机制详解,看这一篇就够了
2. 沙箱机制
3. 双亲委派机制
4. 如何打破双亲委派
5. 详解tomcat打破双亲委派