JVM 自定义类加载器

编写原则
  • 在JDK1.2之前,在自定义类加载器时,总会去重写loadClass方法,从而实现自定义的类加载类,但是JDK1.2之后已不再建议用户去覆盖loadClass方法,而是建议把自定义的类加载逻辑写在findClass方法中
  • 在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自已去编写findClass方法及获取字节码流的方式,使自定义类加载器编写更加简洁。

示例:

package com.classloader;

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 {

    private static final String CLASS_PATH = System.getProperty("java.class.path"); // 编译生成的.class文件的bin目录

    public MyClassLoader() {
        super(ClassLoader.getSystemClassLoader());
    }

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

    
    private byte[] loadClassFile(String className) {
        System.out.println("loadClassFile ....");
        className = className.replace(".", "/");
        File file = new File(CLASS_PATH + "/" + className + ".class");
        try {
            FileInputStream fis = new FileInputStream(file);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;
            while ((b = fis.read()) != -1) {
                baos.write(b);
            }
            fis.close();
            return baos.toByteArray();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return null;
    }

}

说明:这个示例中MyClassLoader这个类没有重写loadClass方法,因此调用loadClass方法实际调用的是父类的loadClass方法,即loadClass 实质上是使用了系统类加载器加载的,而调用 findClass 方法才是真正意义上调用自定义类加载器加载

(1)系统类加载器加载同一个类型时(即类的全限名一致),加载的Class实例只有一个,并且只加载一次。双亲委派模型

MyClassLoader myClassLoader1 = new MyClassLoader();
Class<?> clazz1 = myClassLoader1.loadClass(Sample.class.getName());
MyClassLoader myClassLoader2 = new MyClassLoader();
Class<?> clazz2 = myClassLoader2.loadClass(Sample.class.getName());
System.out.println(myClassLoader1==myClassLoader2);// false
System.out.println(clazz1==clazz2);// true
下面这个实例也可以证明:
同一个类的Class对象只有一个,当该类对象被加载后,就不会再去加载该类对应的Class对象,即使又执行了加载对象的操作;
对于每一个Class对象,可以通过其getClassLoader()方法获得其类加载器的引用,所以Class对象内部有指向其类加载器的引用;
Class<?> clazz1 = ClassLoader.getSystemClassLoader().loadClass(Sample.class.getName());
Class<?> clazz2 = ClassLoader.getSystemClassLoader().loadClass(Sample.class.getName());
System.out.println(clazz1.getClassLoader());// sun.misc.Launcher$AppClassLoader@2a139a55
System.out.println(clazz1==clazz2);// true

(2)用户自定义类加载器加载同一个类型时,可以加载多个Class实例。通过这种方式来破坏双亲委派模型,典型的应用如:Tomcat

MyClassLoader myClassLoader1 = new MyClassLoader();
Class<?> clazz1 = myClassLoader1.findClass(Sample.class.getName());
MyClassLoader myClassLoader2 = new MyClassLoader();
Class<?> clazz2 = myClassLoader2.findClass(Sample.class.getName());
System.out.println(myClassLoader1==myClassLoader2);// false
System.out.println(clazz1==clazz2);// false 

调用 findClass 表示这个类是通过自定义类加载器加载的
clazz1==clazz2 为false说明class实例在堆内存中属于不同的实例。

备注:因为上面 MyClassLoader中的findClass方法是protected类型,可能在其它包下无法访问!因此,需要再重写一个public类型的 loadClass 方法

原文地址:https://www.cnblogs.com/caoxb/p/12735536.html