JDK 源码剖析-classLoader
ClassLoader

在剖析ClassLoader源码之前我们先来了解下类加载器

类加载器

类加载机制

我们都知道,java代码编译完成后会先形成一个.class文件,.class文件再加载到虚拟机中才能运行和使用。

虚拟机把描述类的数据从class文件加载到内存,并对数据做校验,转换,解析,初始化,最终形成可以被虚拟机直接使用的JAVA类型,这是类加载机制。

类加载器

类加载器就是对类加载机制过程的封装。

在《深入理解JAVA虚拟机》这本书中是这么定义类加载器的

“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到JAVA虚拟机外部去实现,以便让应用程序去获取所需要的类,实现这个动作的代码模块就是“类加载器”

怎么来理解上面这一句话?

我们看下类加载机制中第一步-加载的3个主要过程

  • 通过一个类的全限定名来获取描述此类的二进制字节流(说白了根据这个class取一个固定的类名,通过这个类名,获得class文件的二进制流)
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据
  • 在内存中生成一个代表这个类的java.lang.class对象,作为方法区的这个类的各个数据的入口

由上可知,类加载器就是将类加载的第一步在JVM外部实现了。

说到类加载器就不得不说双亲委派模型,在这我今天不讨论,网上资料太多了,只需要知道以下的核心点

一个类加载器接收到加载请求,他不会自己去加载,而是把这个请求委托给父类加载器中加载,而每个层次的加载器都是如此,最终请求都直接传送到顶层的启动类加载器,只有父类加载器无法载入的时候,子加载器才会尝试自己加载

好,那现在我来分析下顶层启动类加载器在java中的实现

ClassLoader源码分析

我们来一行行分析java.lang.class中的loadClass方法

  protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
  {
    synchronized (getClassLoadingLock(name)) { // 1.
      // First, check if the class has already been loaded
      Class<?> c = findLoadedClass(name); //2.
      if (c == null) {
        long t0 = System.nanoTime();
        try {
        //3
          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
        }
                //4
        if (c == null) {
          // If still not found, then invoke findClass in order
          // to find the class.
          long t1 = System.nanoTime();
          c = findClass(name);

         sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
          sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
          sun.misc.PerfCounter.getFindClasses().increment();
        }
      }
      //5
      if (resolve) {
        resolveClass(c);
      }
      return c;
    }
  }

我用数字对代码做了几块的分离,现在我们来分析下这几个点

  • 第1点 getClassLoadingLock(name) 获取这个对象的锁,使用synchronized进行同步,我们来分析下,getClassLoadingLock方法,看代码
private final ConcurrentHashMap<String, Object> parallelLockMap;

protected Object getClassLoadingLock(String className) {
    Object lock = this;
    if (parallelLockMap != null) {
      Object newLock = new Object();
      lock = parallelLockMap.putIfAbsent(className, newLock);
      if (lock == null) {
        lock = newLock;
      }
    }
    return lock;
  }

这儿是基于concurrentHashMap来做的,concurrentHashMap是一个线程安全的map结构,putIfAbsent 表示如果有这个key那么就取出原来hashmap中的这个值,如果没有,把newLock put进去,最后返回一个锁对象。今天不具体分析concurrentHashMap,我们接着往下看

  • 第2点findLoadedClass
Class<?> c = findLoadedClass(name);
protected final Class<?> findLoadedClass(String name) {
    //检查类的名字,文件名是否符合加载标准
    if (!checkName(name))
      return null;
    return findLoadedClass0(name);
 }

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

findLoadedClass0 是一个Native方法,看来这边有JVM来做了一些事,翻看下官方API 文档

Returns the class with the given binary name if this loader has been recorded by the Java virtual machine as an initiating loader of a class with that binary name. Otherwise null is returned.

意思大概是 如果Java虚拟机已将此加载器记录为具有该二进制名称的类的初始加载器,则返回具有给定二进制名称的类。 否则返回null。

  • 第3点这边很简单,看代码采用递归的方式,调用loadClass方法,查询他是否有父类,如果父类不为空,则将父类加载进来,然后再去递归他的父类,也就是双亲委派模型中的像父类去传递。

  • 第4点的代码 ,if (c == null),如果还没有发现这个类对象,则通过findClass(name)方法来加载类,前提是父类无法加载

c = findClass(name);

如果还是没有,那么返回ClassNotFoundException

findClass方法是我们重写外部加载器的关键!

  • 走到这一步的时候,我们已经拿到了最后我们来看下resolveClass(),如果入参resloveClass为true的时候会走到这,而我们classLoader方法是false传入的
protected final void resolveClass(Class<?> c) {
    resolveClass0(c);
  }

private native void resolveClass0(Class<?> c);

首先看下resolve的作用,以下引自官方文档

Links the specified class. This (misleadingly named) method may be used by a class loader to link a class. If the class c has already been linked, then this method simply returns. Otherwise, the class is linked as described in the "Execution" chapter of The Java™ Language Specification.

  • 官方给出的解释是构建一个连接,如果这个类已经被连接,则不返回,如果没有,则连接起来

刚开始的不理解连接的意思,在看的源码上有这么一个注释 invoke class 这是我们在动态代理中看到的一个解释。豁然开朗,这就是动态链接,当多个类引用一个类的时候,通过链接将他们加载到一起,这样,在内存中只会有一块引用类的内存,避免了多块相同的内存块

总结

以上就是我对classLoader的分析,他的实现也是我们常说的双亲委派模型的实现

最后借用《深入理解JAVA虚拟机》中的一句话

使用双亲委派模型来组织加载器之间的关系,有一个显而易见的好处就是JAVA类随着他的类加载器一起具备了一种带有优先级的层次关系 他保护了java.lang.Object类的安全

暂无评论