小程一言
本专栏是对Java知识点的总结。在学习Java的过程中,学习的笔记,加入自己的思考,结合各种资料的整理。
文章与程序一样,一定都是不完美的,因为不完美,才拥有不断追求完美的动力
类加载器的基础
首先要明确类加载器的职责就是加载类。当JVM启动时,系统会使用类加载器将必要的类(如java.lang.*
类库)加载到内存中。Java中的类加载器是递归结构,遵循“双亲委派模型”。
双亲委派模型
核心思想
当一个类加载器接到加载类的请求时,它不会立即去加载该类,而是先将请求委派给它的父类加载器。只有当父类加载器无法完成加载时,子类加载器才会尝试自己加载。
优势
它保证了Java的核心类库(如
java.lang.String
)始终由系统的启动类加载器来加载,避免了“重复加载”问题,并增加了类加载的安全性。
双亲委派模型的加载顺序:
各类加载器的职责
-
启动类加载器(Bootstrap ClassLoader):负责加载JVM的核心类库,如
java.lang.*
包中的类。它是Java中最底层的类加载器,通常由C或C++编写,并且由JVM本身提供。 -
扩展类加载器(Extension ClassLoader):负责加载
jre/lib/ext
目录下的类或JVM扩展目录中的类。它的父类是启动类加载器。 -
系统类加载器(System ClassLoader):负责加载应用程序的类路径(Classpath)中指定的类。它通常是由用户定义的类所在的目录、JAR包等。
-
自定义类加载器(Custom ClassLoader):由开发者自定义的类加载器,可以扩展现有的类加载器,来加载类文件、网络上的类、数据库中的类等。
类加载器的工作流程
类加载器的工作流程可以简单分为以下几个步骤:
- 加载:类加载器根据类名,查找并读取相应的字节码文件(
.class
文件)。 - 验证:JVM会验证字节码文件的有效性,确保它符合Java的语言规范。
- 准备:为类的静态字段分配内存,并为它们赋默认值(如
null
、0
等)。 - 解析:将类中的符号引用转换为直接引用,主要是指将常量池中的符号,如类名、字段名、方法名等,解析为内存地址。
- 初始化:执行类的静态初始化代码(如静态变量的初始化和静态块的执行)。
举例:如何在Java中使用类加载器
启动类加载器、扩展类加载器与系统类加载器
java">public class ClassLoaderExample {public static void main(String[] args) {// 获取当前类加载器ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();System.out.println("系统类加载器:" + systemClassLoader);// 获取扩展类加载器ClassLoader extClassLoader = systemClassLoader.getParent();System.out.println("扩展类加载器:" + extClassLoader);// 获取启动类加载器ClassLoader bootstrapClassLoader = extClassLoader.getParent();System.out.println("启动类加载器:" + bootstrapClassLoader);}
}
输出
系统类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
扩展类加载器:sun.misc.Launcher$ExtClassLoader@5e91993f
启动类加载器:null
解释
系统类加载器
是由ClassLoader.getSystemClassLoader()
返回的,它是JVM默认的类加载器。扩展类加载器
是系统类加载器
的父加载器,它负责加载JVM的扩展库。启动类加载器
是扩展类加载器
的父加载器,它负责加载Java的核心库。
自定义类加载器
通过继承ClassLoader
类来实现自定义类加载器。假设我们有一个自定义的MyClassLoader
,它从特定的目录加载类:
java">import java.io.*;
public class MyClassLoader extends ClassLoader {private String classPath;public MyClassLoader(String classPath) {this.classPath = classPath;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {String path = classPath + name.replace('.', File.separatorChar) + ".class";File file = new File(path);if (file.exists()) {try (FileInputStream inputStream = new FileInputStream(file)) {byte[] bytes = new byte[(int) file.length()];inputStream.read(bytes);return defineClass(name, bytes, 0, bytes.length);} catch (IOException e) {e.printStackTrace();}}return super.findClass(name);}public static void main(String[] args) throws Exception {MyClassLoader myClassLoader = new MyClassLoader("path/to/classes/");// 使用自定义类加载器加载类Class<?> clazz = myClassLoader.loadClass("com.example.MyClass");System.out.println("加载的类: " + clazz.getName());}
}
本例中,定义了一个MyClassLoader
,它从指定的路径加载类文件。当你调用loadClass()
时,它会尝试使用findClass()
方法加载类。如果找不到,系统类加载器会被调用。
例如,在使用自定义类加载器加载完类后,如果不再需要这些类,类加载器本身也会被垃圾回收,类将被卸载。类卸载通常发生在类加载器不再有任何引用,并且类加载器本身被回收时。
类加载器与类冲突
有时会遇到不同的类加载器加载同一个类的情况。如下:
java">public class TestClass {public void printMessage() {System.out.println("Hello from TestClass!");}public static void main(String[] args) throws Exception {MyClassLoader myClassLoader1 = new MyClassLoader("path/to/classes/");MyClassLoader myClassLoader2 = new MyClassLoader("path/to/classes/");Class<?> clazz1 = myClassLoader1.loadClass("TestClass");Class<?> clazz2 = myClassLoader2.loadClass("TestClass");System.out.println("clazz1 == clazz2: " + (clazz1 == clazz2));}
}
在这个例子中,我们创建了两个不同的MyClassLoader
,并用它们分别加载TestClass
类。由于每个MyClassLoader
加载的是不同的TestClass
实例,clazz1 == clazz2
将会输出false
,表明它们是不同的类。