您的位置:首页 > 其它

JVM学习笔记(2)之类加载器双亲委托机制实例

2019-10-21 21:34 387 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/qq_24286273/article/details/102672559

JVM学习笔记(2)之类加载器双亲委托机制实例

1、什么是类加载器的双亲委托机制

类的加载器分为三种,也可以自定义,分别为Bootstrap Class Loader(启动加载器)、Extensions Class Loader(扩展加载器)、Application Class Loader(系统(App)加载器)

  • 启动加载器:

    它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar 或 sun.boot.class.path 路径下的内容),是用原生代码来实现的(C++),并不继承自java.lang.ClassLoader

  • 扩展加载器:

    用来加载Java的扩展库(JAVA_HOME/jre/lib/ext/*.jar或 java.ext.dirs 路径下的内容) .java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类

  • 系统加载器:

    它根据 Java 应用的类路径(ClassPath, java.class.path)来加载。
    一般来说,Java应用的类都是由它来完成加载的

双亲委托机制:

所谓双亲委托机制指的是当Class文件被加载的时候往往是先交给上级加载器加载,如果上级加载器仍然存在上级加载器则继续往上传递,当上级加载器无法加载Class文件的时候才依次往下传递,简单点说就是先往上传递,再根据当前加载器能否加载判断是否往下传递,如果传递到最下面仍然无法加载,则虚拟机加载失败,程序退出,它们的关系如下图


(图片来源于网络)

自定义类加载器:

package com.zh.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

/**
* @ClassName:MyClassLoader
* @Description:TODO
* @author:Jerry
* @Date 2019/10/21 12:55
* @Version 1.0.2
**/
public class MyClassLoader0 extends ClassLoader {

/**
* 自定义加载器的名称
*/
private String classLoaderName;

//文件扩展名
private static final String FILE_EXTENSTION = ".class";

public MyClassLoader0(String classLoaderName) {
//这里的super加与不加无影响?
super();
this.classLoaderName = classLoaderName;
}

public MyClassLoader0(String classLoaderName, ClassLoader parent) {
super(parent);
this.classLoaderName = classLoaderName;
}

@Override
public String toString() {
return "MyClassLoader{" +
"classLoaderName='" + classLoaderName + '\'' +
'}';
}

@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
System.out.println("findClass");
byte[] bytes = loadClassData(className);
return defineClass(className, bytes, 0, bytes.length);
}

private byte[] loadClassData(String className) {
System.out.println("执行了 loadClassData");
className = className.replace(".", File.separator);
byte[] result = null;
try (InputStream is = new FileInputStream(new File(className + FILE_EXTENSTION)); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[256];
int len = 0;
while ((len = is.read(buffer, 0, buffer.length)) != -1) {
bos.write(buffer, 0, len);
}
result = bos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}

public static void testClassLoader(ClassLoader classLoader) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class<?> clazz = classLoader.loadClass("com.zh.classloader.Demo11");
Demo11 o = (Demo11) clazz.newInstance();
System.out.println("加载的实例对象:" + o);
System.out.println("classloader:" + clazz.getClassLoader());
}

public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
MyClassLoader loader1 = new MyClassLoader("loader1");
testClassLoader(loader1);
}
}

打印结果并没有我们想要的:执行了 loadClassData,证明并没有执行我们覆写的findClass()方法,如图.

其实原因很简单:自定的类加载器会寻找它的双亲加载器即系统加载器去加载,系统加载器再去委托上级,只不过它的上级都无法加载,往下重新传到系统加载器,系统加载器成功加载了Demo1.class文件,所以打印的就不是我们自定义的加载器

对代码进行改动:

package com.zh.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

/**
* @ClassName:MyClassLoader
* @Description:TODO
* @author:Jerry
* @Date 2019/10/21 12:55
* @Version 1.0.2
**/
public class MyClassLoader extends ClassLoader {

/**
* 自定义加载器的名称
*/
private String classLoaderName;

/**
* 字节码文件路径
*/
private String path;

//文件扩展名
private static final String FILE_EXTENSTION = ".class";

public MyClassLoader(String classLoaderName) {
//这里的super加与不加无影响?
super();
this.classLoaderName = classLoaderName;
}

public MyClassLoader(String classLoaderName, ClassLoader parent) {
super(parent);
this.classLoaderName = classLoaderName;
}

public void setPath(String path) {
this.path = path;
}

@Override
public String toString() {
return "MyClassLoader{" +
"classLoaderName='" + classLoaderName + '\'' +
'}';
}

@Override
protected Class<?> findClass(S
3ff7
tring className) throws ClassNotFoundException {
System.out.println("findClass");
byte[] bytes = loadClassData(className);
return defineClass(className, bytes, 0, bytes.length);
}

private byte[] loadClassData(String className) {
System.out.println("loadClassData");
className = className.replace(".", File.separator);
byte[] result = null;
try (InputStream is = new FileInputStream(new File(path + className + FILE_EXTENSTION)); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
//            classLoaderName = classLoaderName.replace("\\", ".");
byte[] buffer = new byte[256];
int len = 0;
while ((len = is.read(buffer, 0, buffer.length)) != -1) {
bos.write(buffer, 0, len);
}
result = bos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}

//    public static void testClassLoader(ClassLoader classLoader) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//        Class<?> clazz = classLoader.loadClass("com.zh.classloader.Demo1");
//        Object o = clazz.newInstance();
//        System.out.println(o);
//    }

public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
MyClassLoader loader1 = new MyClassLoader("loader1");
//        loader1.setPath("D:\\jvm_study\\target\\classes\\");
loader1.setPath("C:\\Users\\Jack\\Desktop\\");
Class<?> clazz = loader1.loadClass("com.zh.classloader.Demo11");
System.out.println("classloader:"+clazz.getClassLoader());
System.out.println("class:" + clazz.hashCode());
Object o = clazz.newInstance();
System.out.println(o);
}
}

说明:只是加了个路径,为的是改变class文件的地址,我们知道系统加载器加载的是classpath目录以及我们编写的类的目录,所以认为改动它,我们将class文件拷贝到桌面上的指定目录下,然后加载器去加载,结果如下.

这跟我们想象的结果不一样,好奇怪?好的,我们来分析下原因,我们分析下findClass是什么时候执行的,是在loadClass的时候,如下图

所以其实并未执行到findClass,如果想要让加载器加载到我们放到桌面上的class文件,可以把我们项目目录中生成的class文件删除掉,此时,系统加载器在目录下并不能找到class文件,加载失败,交给我们自定义的加载器,然后在执行findClass的时候,修改了class文件的目录,我们自定义的classloader去桌面的目录下查找,查找到了,应该能执行成功了吧,现在我删除掉了目录下的class文件,如图

执行,结果如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: