玩转Java虚拟机(三)

打卡学习JVM,第三天

本人学习过程中所整理的代码,源码地址

- 如何实现一个自定义类加载器

public class CustomClassLoader extends ClassLoader {
    private String classLoaderName;
    private final String fileExtension = ".class";

    public CustomClassLoader(String classLoaderName) {
        super();//将系统类加载器当作该类加载器的父加载器
        this.classLoaderName = classLoaderName;
    }
    public CustomClassLoader(ClassLoader parent, String classLoaderName) {
        super(parent);//显式指定该类加载器的父加载器
        this.classLoaderName = classLoaderName;
    }
    @Override
    public String toString() {
        return "[" + this.classLoaderName + "]";
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    	System.out.println("classLoaderName:" + classLoaderName);
        System.out.println("className:" + name);
        byte[] b = loadClassData(name);
        return defineClass(name, b, 0, b.length);
    }
    private byte[] loadClassData(String name) {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;
        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            is = new FileInputStream(new File(name + this.fileExtension));
            baos = new ByteArrayOutputStream();
            int ch = 0;
            while (-1 != (ch = is.read())) {
                baos.write(ch);
            }
            data = baos.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                baos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return data;
    }
    public static void test(ClassLoader classLoader) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class<?> clazz = classLoader.loadClass("classloader.MyTest");
        Object object = clazz.newInstance();
        System.out.println(object);
    }
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        CustomClassLoader customClassLoader = new CustomClassLoader("loader");
        test(customClassLoader);
    }
}
/*classloader.MyTest@15db9742
sun.misc.Launcher$AppClassLoader@73d16e93*/

从运行结果可以看出,加载MyTest类的类加载器并不是自定义加载器,而是系统类加载器,因此我们重写的方法根本没有被调用,那么造成这种现象的原因是什么?
主要是在调用自定义类加载器的构造函数创建对象时,指定了其父类加载器为系统类加载器,又因为类加载的双亲委托机制,才会导致这种情况的发生。
此时只需要将MyTest类的class文件从classpath中删除,并在在另一个路径下保存该类的字节码文件,当程序运行时默认的系统类加载器便无法从系统路径上找到MyTest类的class文件,最后会调用自定义加载器加载指定路径上的MyTest.class文件

    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        CustomClassLoader customClassLoader = new CustomClassLoader("loader");
//        customClassLoader.setPath("G:\\GitHub\\jvm-study\\build\\classes");
        customClassLoader.setPath("C:\\Users\\lss\\Desktop");
        Class<?> clazz = customClassLoader.loadClass("classloader.MyTest");
        System.out.println("class:" + clazz.hashCode());
        Object object = clazz.newInstance();
        System.out.println(object);
    }

当我们创建两个自定义类加载器并对MyTest类进行加载时,此时两次加载出的类的哈希值是不一样的,这说明同一个类被加载了两次,这么将牵涉到命名空间这个概念。

- 命名空间

  • 每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。
  • 在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
  • 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类

当类加载器在同一个命名空间加载类时,此时所加载的类都是同一个实例(哈希值相同)。

- 不同类加载器的命名空间关系

  • 同一个命名空间内的类是相互可见的——
  • 子加载器的命名空间包含所有父类加载器的命名空间,因此由子加载器加载的类能看见父加载器加载的类,例如系统类加载器加载的类能看见根类加载器加载的类
  • 由父加载器加载的类不能看见子加载器加载的类
  • 如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见

总结:

今天实现了一个自定义类加载器以及对自定义类加载器的加载机制进行了探讨。

发布了10 篇原创文章 · 获赞 20 · 访问量 962

猜你喜欢

转载自blog.csdn.net/qq_41982594/article/details/104704557
今日推荐