java-类加载机制

类加载重复、重复、重复。

ClassLoader

.class 文件什么时候会被类加载器加载到 JVM 中?

  • 执行 new 操作的时候
  • 使用 Class.forName(“包路径+类名”)、Class.forName(”包路径+类名”, ClassLoader)、ClassLoader.loadClass(“包路径+类名”) 的时候就触发了类加载器去类对应的类路径
  • 另外需要注意的是除了 new 操作外,其他几种方式加载字节码到内存后只是生产一个 Class 对象,要产生具体的对象实例还需要使用 Class 对象的 .newInstance() 函数来创建

Java 中原生的三种 ClassLoader

AppClassloader

  • 系统加载器,负责在 JVM 启动时,加载来自命令 java 中的-classpath 或者 java.class.path 系统属性或者 CLASSPATH 操作系统属性所指定的 JAR 类包和类路径。
  • 调用 ClassLoader.getSystemClassLoader() 可获取该类加载器
  • 如果没有特别指定,则用户自定义的任何类加载器都将该类作为它的父加载器
System.out.println(System.getProperty("java.class.path"));

ExtClassLoader

  • 负责加载 Java 的扩展类库,默认加载 JAVA_HOME/jre/lib/ext/ 目录下的所有 Jar 包或者由 java.ext.dirs 系统属性指定的 Jar 包。
  • 放入这个路径下的Jar 包对于 APPClassLoader 加载器都是可见的,因为 ExtClassloader 是 APPClassLoader 的父加载器,并且 Java 类加载器采用委托机制。
System.out.println(System.getProperty("java.ext.dirs"));  

BoostrapClassloader

  • 引导类加载器、又称启动类加载器,是最顶层的类加载器
  • 主要来加载 Java 核心类,如 rt.jarresources.jarcharses.jar
  • 注意他不是 java.lang.ClassLoader 的子类,而是由 JVM 自身实现的,该类为 C 语言实现,所以严格来说他不属于 Java 类加载范畴,Java 程序访问不到该加载器。

查找该类加载器扫描类的路径:

URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();    
for (int i = 0; i < urls.length; i++) {    
    System.out.println(urls[i].toExternalForm());    
}   

类加载器的构造

JVM 如何构建 Classloader 的,具体是 rt.jar 包里面的 sun.misc.Launcher 类。

public Launcher()  
{  
    ExtClassLoader localExtClassLoader;  
    try  
    {  //(1)首先创建了ExtClassLoader
        localExtClassLoader = ExtClassLoader.getExtClassLoader();  
    }  
    catch (IOException localIOException1)  
    {  
        throw new InternalError("Could not create extension class loader");  
    }  
    try  
    {  //(2)然后以ExtClassloader作为父加载器创建了AppClassLoader
        this.loader = AppClassLoader.getAppClassLoader(localExtClassLoader);  
    }  
    catch (IOException localIOException2)  
    {  
        throw new InternalError("Could not create application class loader");  
    }  //(3)这个是个特殊的加载器后面会讲到,这里只需要知道默认下线程上下文加载器为appclassloader
    Thread.currentThread().setContextClassLoader(this.loader);  

    ................
} 

(1)创建 ExtClassLoader 类加载器

public static ExtClassLoader getExtClassLoader()  
    throws IOException  
    {  
      File[] arrayOfFile = getExtDirs();  
      try  
      {  
        (ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction()  
        {  
          public Launcher.ExtClassLoader run()  
            throws IOException  
          {  
            int i = this.val$dirs.length;  
            for (int j = 0; j < i; j++) {  
              MetaIndex.registerDirectory(this.val$dirs[j]);  
            }  
            //(5) 此处说明了 ExtClassLoader 的父加载器为 null
            return new Launcher.ExtClassLoader(this.val$dirs);  
          }  
        });  
      }  
      catch (PrivilegedActionException localPrivilegedActionException)  
      {  
        throw ((IOException)localPrivilegedActionException.getException());  
      }  
    }  

//(6) ExtClassLoader 类加载类扫描路径为:java.ext.dirs
private static File[] getExtDirs()  
{  
    String str = System.getProperty("java.ext.dirs");  
    File[] arrayOfFile;  
    if (str != null)  
    {  
        StringTokenizer localStringTokenizer = new StringTokenizer(str, File.pathSeparator);  

        int i = localStringTokenizer.countTokens();  
        arrayOfFile = new File[i];  
        for (int j = 0; j < i; j++) {  
            arrayOfFile[j] = new File(localStringTokenizer.nextToken());  
        }  
    }  
    else  
    {  
        arrayOfFile = new File[0];  
    }  
    return arrayOfFile;  
}  

(2)以 ExtClassLoader 作为父加载器创建了 APPClassLoader

public static ClassLoader getAppClassLoader(final ClassLoader paramClassLoader)  
    throws IOException  
    {  //(8)
      String str = System.getProperty("java.class.path");  
      final File[] arrayOfFile = str == null ? new File[0] : Launcher.getClassPath(str);  

      (ClassLoader)AccessController.doPrivileged(new PrivilegedAction()  
      {  
        public Launcher.AppClassLoader run()  
        {  
          URL[] arrayOfURL = this.val$s == null ? new URL[0] : Launcher.pathToURLs(arrayOfFile);  

          return new Launcher.AppClassLoader(arrayOfURL, paramClassLoader);  
        }  
      });  
    } 

(5)、扩展设置父加载器

public ExtClassLoader(File[] paramArrayOfFile)
    throws IOException
    {
     //(7)第一个参数,就是父加载器的设置,这里传递了null。
      super(null, Launcher.factory);
      SharedSecrets.getJavaNetAccess()
        .getURLClassPath(this).initLookupCache(this);
    }

(2)、创建 ExtClassloader 作为父加载器创建 APPClassLoader,

public static ClassLoader getAppClassLoader(final ClassLoader paramClassLoader)  
    throws IOException  
    {  //(8)  AppClassLoader 类加载扫描路径为:java.class.path
      String str = System.getProperty("java.class.path");  
      final File[] arrayOfFile = str == null ? new File[0] : Launcher.getClassPath(str);  

      (ClassLoader)AccessController.doPrivileged(new PrivilegedAction()  
      {  
        public Launcher.AppClassLoader run()  
        {  
          URL[] arrayOfURL = this.val$s == null ? new URL[0] : Launcher.pathToURLs(arrayOfFile);  
         //(9)设置父类是 ExtClassLoader 
          return new Launcher.AppClassLoader(arrayOfURL, paramClassLoader);  
        }  
      });  
    }  

(9)具体操作

AppClassLoader(URL[] paramArrayOfURL, ClassLoader paramClassLoader)
{
    //paramClassLoader就是ExtClassloader
    super(paramClassLoader, Launcher.factory);
    this.ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
    this.ucp.initLookupCache(this);
}

类加载器原理

类加载(双亲委派机制)优点:

  • 避免重复加载,父类存在子类不必加载
  • 安全因素,避免子加载加载相同的类,替换父加载加载的类

委托机制源码

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            // 首先从jvm缓存查找该类
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // 判断父类是否为空,否则委托给父类加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        // 如果父类加载器为null,则委托给 boostrap 加载器加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                // 当字节码加载到内存中后进行链接操作,对文件格式和字节码验证,
                // 并为 static 字段分配空间初始化,符号引用转为直接引用,访问
                // 控制,方法覆盖。
                resolveClass(c); 
            }
            return c;
        }
   }

特殊的类加载器 ContextClassLoader

ContextClassLoader 是一种与线程相关的类加载器,类似 ThreadLocal,每个线程对应一个上下文类加载器,在使用时,都有下面经典结构

//获取当前线程上下文类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {//设置当前线程上下文类加载器为targetTccl
    Thread.currentThread().setContextClassLoader(targetTccl);
    //doSomething 
    doSomething();
} finally {//设置当前线程上下文加载器为原始加载器
    Thread.currentThread().setContextClassLoader(classLoader);
}

步骤:

  • 首先获取当前线程的线程上下文类加载器并保存到方法栈,然后设置当前线程上下文类加载器
  • doSomething 里面则调用 Thread.currentThread().getContextClassLoader() ,获取当前线程上下文类加载器做某些事情
  • 设置当前线程上下文类加载器为老的类加载器

一些特例(违反委托机制)

  • Java 中的 SPI(Service Provider Interface) 是面向接口编程的,服务规则提供者会在 JRE 的核心API里面提供服务访问接口,而具体实现则有其他开发商提供。
  • 我们知道 Java 核心 API,比如 rt.jar 包,是使用 Bootstrap ClassCloader 加载的,而用户提供的 Jar 再由 AppClassLoader 加载。
  • 但是我们知道,如果一个类由类加载器 A 加载,那么这个类依赖类也是由相同的类加载器加载的。
  • 那么这些API 类里面依赖的类也应该是由 Boostrap ClassLoader 来加载,但是上面又说了用户提供的 Jar 包由 APPClassLoader加载,所以需要一种违反双亲委派模型的方法
  • 解决上述问题:线程上下文类加载器

在实现该demo之前,首先应该将 MySQL的 jar 引入到当前的 classpath中。

public class MysqlTest {

    public static void main(String[] args) {
        // 简单测试设置当前线程加载器为 ExtendClassLoader      Thread.currentThread().setContextClassLoader(MysqlTest.class.getClassLoader().getParent());
        //(1) 获取loader 
        ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
        //(2) 遍历
        Iterator<Driver> iterator = loader.iterator();
        while (iterator.hasNext()) {
            Driver driver = (Driver) iterator.next();
            System.out.println("driver:" + driver.getClass() + ",loader:" + driver.getClass().getClassLoader());
        }
        //(3) 输出当前线程的加载器
        System.out.println("current thread contextloader:" + Thread.currentThread().getContextClassLoader());
        //(4) 输出ServiceLoader的加载器
        System.out.println("ServiceLoader loader:" + ServiceLoader.class.getClassLoader());
    }
}

此处主要想说明的就是:ContextClassLoader 的作用就是为了破坏java类加载委托机制,JDBC 规范定义了一个 JDBC 接口,然后使用 SPI 机制提供的一个叫做 ServiceLoader 的 java 核心 API(rt.jar中提供)用来扫描服务实现类,服务实现者提供的 Jar,比如 MySQL 驱动则是我们的 ClassPath 下面,从上文知道默认线程上下文类加载器是 APPClassLoader,所以例子里面没有显示在调用 ServiceLoader 前设置线程上下文类加载器为 APPClassLoader,ServiceLoader 内部则获取当前线程上下文来加载服务实现者的类,这里加载了 ClassPath 下的 MySQL 的驱动实现。

同时,当我们把上下文类加载器设置为 ExtClassLoader ,则默认会去 JAVA_HOME/jre/lib/ext 目录下面去扫描,当然是不会加载到了。

总结:当父类加载器需要加载子类加载器中的资源时,可以通过设置和获取线程上下文类加载器来实现。

参考:http://gitbook.cn/books/5a7719e7367c47172bea2b53/index.html


文章作者: HoldDie
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 HoldDie !
评论
 上一篇
ElasticSearch-实战入门(二) ElasticSearch-实战入门(二)
尖刺,玖拾胜利! boost 权重计算解决问题:当搜索标题中包含 java 的帖子,同时标题中包含hadoop或elasticsearch就优先搜索出来,同时有hadoop的帖子要比有elasticsearch的帖子优先显示。 知识点:
2018-04-19
下一篇 
ElasticSearch-实战入门(一) ElasticSearch-实战入门(一)
在门外徘徊了两天,包括官方文档粗略过了一下,决定入门看看,望自己坚持一步一步走下去,最后希望达到的目的是完美解决工作中的问题。 批量插入数据:POST /forum/article/_bulk { "index" : {"_id":1 }}
2018-04-17
  目录