类加载器及双亲委派模型
前言:首先列出我前面提到的三个问题。1.jvm什么时候会去加载Class文件并初始化类呢?2. jvm是如何加载Class文件的呢?3. jvm加载一个Class文件要经过哪些步骤呢?上一篇博文我们已经阐述了第一个问题 类加载的时机,本文主要阐述第二个问题。
一 类加载器
在类加载的第一个阶段“加载”阶段,需要通过一个类的全限定名来获取定义此类的二进制字节流,完成这个动作的就是类加载器。这一动作是放在Java虚拟机外部去实现的,以便让应用程序自己决定如何获取所需的类。
JVM预定义的三种类型类加载器:
- 启动(Bootstrap)类加载器:是用本地代码实现的类装入器,它负责将 Java_Home/lib 下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
- 标准扩展(Extension)类加载器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将Java_Home /lib/ext 或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
- 系统(System)类加载器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。
除此之外,还有自定义的类加载器,它们之间的层次关系被称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,而这种父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)。
二 双亲委派模型
双亲委派机制描述:
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
下面我们可以从源码来深入理解双亲委派机制:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 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 = 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.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
三 自定义类加载器
从上面对于java.lang.ClassLoader的loadClass(String name, boolean resolve)方法的解析来看:
1、如果不想打破双亲委派模型,那么只需要重写findClass方法即可。
2、如果想打破双亲委派模型,那么就重写整个loadClass方法。
当然,我们自定义的ClassLoader不想打破双亲委派模型,所以自定义的ClassLoader继承自java.lang.ClassLoader并且只重写findClass方法。
package aboutJvm;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class MyClassLoader extends ClassLoader{
//.class文件所在路径
private String path = "F:\\test";
public MyClassLoader(){
}
public MyClassLoader(ClassLoader parent){
super(parent);
}
/**
* 重写findClass方法
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = loadClassData(name);
if(data == null){
throw new ClassNotFoundException();
}else{
return this.defineClass(name, data, 0, data.length);
}
}
/**
* 获取类的class文件的字节数组
*/
public byte[] loadClassData(String className){
String fileName = path + File.separatorChar
+ "Person.class";
System.out.println("fileName: "+fileName);
try {
FileInputStream fis = new FileInputStream(new File(fileName));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;
while((b = fis.read())!=-1){
baos.write(b);
}
return baos.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> clazz = myClassLoader.loadClass("aboutJvm.Person");//和MyClassLoader类同包
Object obj = clazz.newInstance();
System.out.println("类加载器: "+obj.getClass().getClassLoader());
}
}
/**
OutPut:
类加载器:sun.misc.Launcher$AppClassLoader@5736ab79
*/
由输出结果可见,程序并没有调用我们自己定义的类加载器。这是因为在CLASSPATH下有Person.class,那么自然是由Application ClassLoader来加载这个.class文件了。
解决办法:
- 把编译后的Person.class文件拷贝到F:\test 目录下,并且删除CLASSPATH下的Person.class。此时Application ClassLoader就会把这个.class文件交给下一级用户自定义ClassLoader去加载了。
- main方法中的第一行这么写:
MyClassLoader myClassLoader = new MyClassLoader(ClassLoader.getSystemClassLoader().getParent());
即把自定义ClassLoader的父加载器设置为Extension ClassLoader,这样父加载器加载不到Person.class,就交由子加载器MyClassLoader来加载了。
此时output:
fileName: F:\test\Person.class
类加载器: aboutJvm.MyClassLoader@2d7fc1e7
编写自定义类加载器遇到的问题:java.lang.NoClassDefFoundError,这篇博文很好的解决了我的问题。
https://www.cnblogs.com/chenjfblog/p/7904024.html