神奇的ClassLoader----自定义ClassLoader后发生的问题

214 阅读4分钟

自定义ClassLoader

自定义ClassLoader()

public class MyClassLoader extends ClassLoader {
    //定义默认的class存放路径
    private final static Path DEFAULT_CLASS_PATH = Paths.get("/Users/xxx/Desktop/classloader1");
    private final Path classDir;

    //使用默认的class路径
    public MyClassLoader() {
        super();
        this.classDir = DEFAULT_CLASS_PATH;
    }

    //允许传递指定的class路径
    public MyClassLoader(Path classDir){
        super();
        this.classDir = Paths.get(String.valueOf(classDir));;
    }

    //允许传递指定的class路径和父类加载器
    public MyClassLoader(Path classDir, ClassLoader parent){
        super();
        this.classDir = Paths.get(String.valueOf(classDir));;
    }

    /**
     *  根据类的全名称转换成文件的全路径重写findClass方法
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classBytes = this.readClassBytes(name);
        //为null则抛出异常
        if(null == classBytes || classBytes.length == 0){
            throw new ClassNotFoundException("Cant not load the calss" + name);
        }
        return this.defineClass(name,classBytes,0,classBytes.length);
    }

    //将class文件读入内存
    private byte[] readClassBytes(String name) throws ClassNotFoundException {
        String calassPath = name.replace(".","/");
        Path classFullPath = classDir.resolve(Paths.get(calassPath + ".class"));
        if (!classFullPath.toFile().exists()){
            throw new ClassNotFoundException("The class " + name + "not found");
        }
        try(ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            Files.copy(classFullPath,baos);
            return baos.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException("load the class " + name + "occur error.",e);
        }

    }

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

defineClass方法

   protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }

参数解释

String name : 自定义类的名字,就是findClass方法中的 String name
byte[] b    : class文件的二进制字节数组。
int off     : 偏移量
int len     : 表示从偏移量off开始往后读取多少个byte

loadClass方法

protected Class<?> loadClass(String name, boolean resolve)
  throws ClassNotFoundException
{
  synchronized (getClassLoadingLock(name)) {
    // 首先,检查类是否已经加载;见源码2
    Class<?> c = findLoadedClass(name);
    //如果类 == null
    if (c == null) {
      long t0 = System.nanoTime();
      try {
        //并且当前类存在父类加载器,则调用父类加载器的loadClass(name,fase)方法进行加载
        if (parent != null) {
          //在这里赋值resolve为false,所以最后的if不会执行
          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
      }

      //如果当前类的所有父类加载器都没有成功加载class,则尝试调用当前类加载器的findClass方法对其进行加载
      if (c == null) {
        long t1 = System.nanoTime();
        //当前类加载器的findClass方法,该方法就是我们自定义加载器需要重写的方法。
        c = findClass(name);

        // 当前类加载器的findClass方法,该方法就是我们自定义加载器需要重写的方法。
        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
        sun.misc.PerfCounter.getFindClasses().increment();
      }
    }
    //这也就解释了为什么通过类加载器加载类并不会导致类的初始化。
    if (resolve) {
      resolveClass(c);
    }
    return c;
  }
}

问题:相同类加载器加载同一个class的实例是相同的吗?

测试源码

 private static void demo2() throws ClassNotFoundException {
        //同一个类加载器的不同实例去加载同一个class文件,则会在堆内存和方法区产生多个class对象
        MyClassLoader classLoader = new MyClassLoader(Paths.get("/Users/yingtaowang/Desktop/classloader1"),null);
        MyClassLoader classLoader1 = new MyClassLoader(Paths.get("/Users/yingtaowang/Desktop/classloader1"),null);

        Class<?> aClass = classLoader.loadClass("com.example.logbackdemo.com.wangwenjun.concurrent.chapter10.Hello");
        Class<?> bClass = classLoader1.loadClass("com.example.logbackdemo.com.wangwenjun.concurrent.chapter10.Hello");

        System.out.println(aClass.getClassLoader());
        System.out.println(bClass.getClassLoader());

        System.out.println(aClass.hashCode());
        System.out.println(bClass.hashCode());
        System.out.println(aClass == bClass);
    }

上述代码的运行结果:

MyClassLoader{classDir=/Users/yingtaowang/Desktop/classloader1}
MyClassLoader{classDir=/Users/yingtaowang/Desktop/classloader1}
752848266
815033865
false

对于相同类加载器的不同实例加载同一个class文件,会得出不同的两个class实例

我们根据这个问题来再看一次loadClass的源码

protected Class<?> loadClass(String name, boolean resolve)
  throws ClassNotFoundException
{
  synchronized (getClassLoadingLock(name)) {
    // 首先,检查类是否已经加载;关键在findLoadedClass(name),见源码2
    Class<?> c = findLoadedClass(name);
    //如果类 == null
    if (c == null) {
      long t0 = System.nanoTime();
      try {
        //并且当前类存在父类加载器,则调用父类加载器的loadClass(name,fase)方法进行加载
        if (parent != null) {
          //在这里赋值resolve为false,所以最后的if不会执行
          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
      }

      //如果当前类的所有父类加载器都没有成功加载class,则尝试调用当前类加载器的findClass方法对其进行加载
      if (c == null) {
        long t1 = System.nanoTime();
        //当前类加载器的findClass方法,该方法就是我们自定义加载器需要重写的方法。
        c = findClass(name);

        // 当前类加载器的findClass方法,该方法就是我们自定义加载器需要重写的方法。
        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
        sun.misc.PerfCounter.getFindClasses().increment();
      }
    }
    //这也就解释了为什么通过类加载器加载类并不会导致类的初始化。
    if (resolve) {
      resolveClass(c);
    }
    return c;
  }
}

源码2

findLoadedClass(String name) And findLoadedClass0(String name)

protected final Class<?> findLoadedClass(String name) {
  if (!checkName(name))
    return null;
  return findLoadedClass0(name);
}

private native final Class<?> findLoadedClass0(String name);

在类加载器进行类加载的时候,首先会到加载记录表也就是缓存中,查看该类是否已经被加载过了,如果已经被加载过了,就不会重复加载,否则将会认为其是首次加载。

所以结论是:相同的class被不同的ClassLoader加载后会对应多个class实例