详解Java类加载器与双亲委派机制
Java类加载器是Java虚拟机(JVM)的一个重要组成部分。类加载器负责将class文件从文件系统、网络等位置加载到内存中的虚拟机中,从而使得Java程序能够正确运行。在Java中,类加载器采用了“双亲委派机制”(Parent Delegation Model)来管理和加载类。
双亲委派机制
Java类加载器通过双亲委派机制来管理和加载类。这种机制的基本思想是,当一个类加载器需要加载某个类时,它首先会委派给它的父加载器来尝试加载该类。如果父加载器无法加载该类,才会由该加载器自己来加载。这个过程会一直持续下去,直到顶层的父加载器(Bootstrap ClassLoader)无法加载该类。如果仍然无法找到该类的定义,就会抛出ClassNotFoundException。
双亲委派机制的好处在于,它保证了Java类的统一性。由于父加载器会优先加载类,所以相同的类只会被加载一次。同时,由于每个类加载器都只加载比它自己所处的层次更低的类,因此可以保证类加载器的完全独立性。
类加载器的种类
Java虚拟机中内置了三个类加载器:Bootstrap ClassLoader、Extension ClassLoader和System ClassLoader。
- Bootstrap ClassLoader:也称为根加载器,是Java虚拟机内置的类加载器,用于加载核心类库。它并不是一个普通的Java类,它由C++编写,通常表示为null。Bootstrap ClassLoader不需要父加载器。
- Extension ClassLoader:用于加载Java的扩展类库,它是由Java编写的类加载器,是System ClassLoader的父加载器。它会从java.ext.dirs系统属性指定的目录中加载类文件。
- System ClassLoader:也称为应用程序类加载器,用于加载应用程序的类文件。它是由Java编写的类加载器,是Extension ClassLoader的父加载器。它会从CLASSPATH环境变量指定的目录中加载类文件。
当Java虚拟机启动时,会将Bootstrap ClassLoader作为根加载器创建,并所有其他的类加载器都是由它派生出来的。
加载器示例一
假设我们有项目结构如下:
Project
├── com
│ └── example
│ └── Demo.class
└── Test.class
其中,Test类中引用了Demo类。
我们创建一个自定义的类加载器,如下所示:
public class MyClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if ("com.example.Demo".equals(name)) {
byte[] bytes = loadClassData(name);
return defineClass(name, bytes, 0, bytes.length);
}
return super.loadClass(name);
}
private byte[] loadClassData(String className) throws ClassNotFoundException {
try {
InputStream is = getClass().getClassLoader().getResourceAsStream(className.replace(".", "/") + ".class");
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
int nextValue = is.read();
while (nextValue != -1) {
byteStream.write(nextValue);
nextValue = is.read();
}
return byteStream.toByteArray();
} catch (IOException e) {
throw new ClassNotFoundException("Class file not found: " + className, e);
}
}
}
然后我们在Test类中,使用我们自定义的类加载器加载Demo类:
public class Test {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
MyClassLoader classLoader = new MyClassLoader();
Class<?> clazz = classLoader.loadClass("com.example.Demo");
Object obj = clazz.newInstance();
System.out.println(obj.getClass().getClassLoader());
}
}
运行结果为:
com.example.MyClassLoader@xxxxxx
我们发现,Demo类由我们自定义的类加载器MyClassLoader加载,而不是默认的系统类加载器。这是因为我们在自定义的类加载器中,重写了loadClass方法,并指定了Demo类的加载方式。
加载器示例二
现在我们将Demo类放在$JAVA_HOME/lib目录下,然后在程序中尝试加载它:
public class Test {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass("com.example.Demo");
Object obj = clazz.newInstance();
System.out.println(obj.getClass().getClassLoader());
}
}
运行结果为:
sun.misc.Launcher$AppClassLoader@xxxxxx
我们发现,Demo类由系统类加载器加载。这是因为系统类加载器是Extension ClassLoader的子类,而Demo类在$JAVA_HOME/lib目录下,它正是Extension ClassLoader所加载的类的范畴。
总结
Java类加载器和双亲委派机制是Java虚拟机的核心部分,它保证了Java类的统一性,并且为我们提供了非常灵活的扩展机制。对于开发人员而言,我们可以通过自定义类加载器的方式,来实现独立的类加载环境,从而更好地管理和保护我们的代码。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解Java类加载器与双亲委派机制 - Python技术站