java-源码解读-java类加载机制

作为一个java程序员,估计没有人不知道java的类加载机制是委托父加载器加载,也知道java有这么几大类加载器,分别是BootStrapClassLoader,ExtClassLoader,AppClassLoader以及自定义类加载器。但对于他们的之间的关系是怎样建立起来,估计很少有人说得清楚,下面就通过最权威的源码(jdk1.8),回答以上问题。

ClassLoader

java用ClassLoader表示一个类加载器,所有其它类加载器都继承至ClassLoader。下面通阅读ClassLoader源码,来了解ClassLoader的实现原理。

/**ClassLoader成员变量parent,父加载器**/

privatefinal ClassLoader parent;

/**ClassLoader成员变量scl,其实就是SystemClassLoader**/

privae static ClassLoader scl;

/**构造方法**/

protected ClassLoader(ClassLoader parent){

   /**checkCreateClassLoader方法只做了安全检查**/

   /**this调用了另外一个构造函数**/

   this(checkCreateClassLoader(), parent);

}

private ClassLoader(Void unused, ClassLoader parent){

   /**也是传入了父加载器,并赋值给了parent成员变量**/

   this.parent = parent;

   /**。。。省去了一部分代码**/

} 

从构造方法可以看出,构造一个类加载器,需要传入一个父加载器。从成员变量parent及构造方法可以看出,父加载器中的“父”是逻辑上意义,不是extends关系。

从上面的代码可以看出,构造类加载器需要指定父加载器。那么问题来了,如果调用的是无参构造器,那父加载器是谁呢?继续看代码。

/**无参构造器**/

protected ClassLoader(){

   this(checkCreateClassLoader(), getSystemClassLoader());

   /**

   通过代码可以看出,无参构造器的父加载器是getSystemClassLoader()的返回值,

   其实他的返回值就是AppClassLoader,也叫SystemClassLoader,后面会介绍getSystemClassLoader()方法。这就是为什么我们自己定义的加载器,如果没有指定父加载器,默认的父加载器就是AppClassLoader的原因。

   */

}

下面看下getSystemClassLoader()方法。

publicstatic ClassLoader getSystemClassLoader(){

   /**又调用了initSystemClassLoader()方法**/

   initSystemClassLoader();

   /**scl是一个ClassLoader的成员变量,上面有提到**/

   if(scl ==null){

       returnnull;

   }

   /**。。。省去了一部分代码。

   返回scl,就是SystemClassLoader,下面会提到

   **/

   return scl;

}

getSystemClassLoader()方法又调用的initSystemClassLoader()方法。

privatestaticsynchronizedvoid initSystemClassLoader(){

   if(!sclSet){

       if(scl !=null)

            thrownew IllegalStateException("recursive invocation");

       /**获取Launcher类**/

       sun.misc.Launcher l = sun.misc.Launcher.getLauncher();

       if(l !=null){

            Throwable oops =null;

            /**从Launcher类中获取ClassLoader,并赋值给了scl,

            其实这个scl就是AppClassLoader(后面会讲到)

            */

            scl = l.getClassLoader();

       }                                              

           /**…省去了一些代码**/

}

从以上代码可以看出,我们自己定义的加载器,如果没有指定父加载器,默认的父加载器就是AppClassLoader。那么类加载器又是如何加载类的呢?

类加载器加载类的时候调用加载器的loadClass方法。

 

protected Class loadClass(String name,boolean resolve)

   throws ClassNotFoundException{

   synchronized(getClassLoadingLock(name)){

   /**首先查看这个类是否被加载,加载的类都会被缓存**/

   Class c = findLoadedClass(name);

   /**没有加载过的类才会被加载**/

   if(c ==null){

       long t0 = System.nanoTime();

       try{

        /**首先查看父加载器是否为空,不为空就委托给父加载器去加载**/

            if(parent !=null){

                /**委托给父加载器加载**/

                c = parent.loadClass(name,false);

            }else{

            /**如果父加载器为空,就委托BootstrapClassLoader去加载,

                自定义的加载器父加载器默认是AppClassLoader,上面已经说过了

             */

                c = findBootstrapClassOrNull(name);

            }

       }catch(ClassNotFoundException e){

       }

       /**如果父加载器及BootstrapClassLoader都没加载到**/

       if(c ==null){

            long t1 = System.nanoTime();

       /**如果父加载器及BootstrapClassLoader都没加载到才调用自己的findClass方法去加载这个类,下面会分析findClass方法

       */

            c = findClass(name);

        /**。。。省去一部分代码**/

        }

   }

   if(resolve){

       resolveClass(c);

   }

   return c;

   }

}

从上面可以出类的加载机制为委托父类加载机制:先递归调用父加载器的loadClass方法,一直到BootstrapClassLoader为止,再调用自己findClass方法去加载。

下面看ClassLoader的findClass方法。

protected Class findClass(String name)throwsClassNotFoundException{

   thrownew ClassNotFoundException(name);

}

findClass方法是ClassLoader的一个空实现,留给子类去实现,不同的子类可能有不同的实现。不如看一下最常用的URLClassLoader是怎样实现这个方法的。

URLClassLoader

URLClassLoader的成员变量:

privatefinal URLClassPath ucp; /**用于加载资源**/

URLClassLoader的构造方法:

public URLClassLoader(URL[] urls, ClassLoader parent,

                      URLStreamHandlerFactoryfactory){

   super(parent);

   SecurityManager security =System.getSecurityManager();

   if(security !=null){

       security.checkCreateClassLoader();

   }

   acc = AccessController.getContext();

   ucp =new URLClassPath(urls, factory, acc);

}

从构造方法可以看出,要构造URLClassLoader,也必须传入一个父加载器,同上面的结论一致。

URLClassLoaderfindClass方法:

protected Class findClass(final String name)

   throws ClassNotFoundException{

   final Class result;

   try{

       result = AccessController.doPrivileged(

            new PrivilegedExceptionAction>(){

                public Class run()throws ClassNotFoundException {

                    /**把类的.替换成文件路径的**/

                    String path = name.replace('.','/').concat(".class");

                    /**ucp是一个URLClassPath类型的成员变量,

                    通过这行代码可以看出,加载类也是通过加载资源的方式去实现的

                    */

                    Resource res = ucp.getResource(path,false);

                    if(res !=null){

                        try{

                            /**加载到资源后,把资源转换成Class**/

                            return defineClass(name, res);

                        }catch(IOException e){

                            thrownew ClassNotFoundException(name, e);

                        }

                    }else{

                        returnnull;

                    }

                }

            }, acc);

   }catch(java.security.PrivilegedActionException pae){

       throw(ClassNotFoundException) pae.getException();

   }

   if(result ==null){

       thrownew ClassNotFoundException(name);

   }

   return result;

}

URLClassPath加载资源

直接看URLClassPath是怎样加载资源的。

/**var1是类的路径**/

Resource getResource(final String var1,boolean var2){

   final URL var3;

   try{

       /**把类的路径转成URL**/

       var3 =new URL(this.base, ParseUtil.encodePath(var1,false));

   }catch(MalformedURLException var7){

       thrownew IllegalArgumentException("name");

   }

   final URLConnection var4;

   try{

       if(var2){

            URLClassPath.check(var3);

       }

       /**通过URL的openConnection打开一个连接**/

       var4 = var3.openConnection();

       /**获取文件的输入流**/

       InputStream var5 = var4.getInputStream();

       /**。。。省去部分代码**/

   /**然后返回一个带有输入流的Resource**/

   returnnew Resource(){

       public String getName(){

            return var1;

       }

       public URL getURL(){

            return var3;

       }

       public URL getCodeSourceURL(){

            return Loader.this.base;

       }

       public InputStream getInputStream()throws IOException {

            return var4.getInputStream();

       }

       publicint getContentLength()throws IOException {

            return var4.getContentLength();

       }

   };

}

从以上代码可以看出,java加载类是通过加载资源的方式是实现的,即打开一个文件输入流,包装成Resource,然后通过调用defineClass,把输入流转成一个Class文件。defineClass最后调用一个native的本地方法把输入流转成class,就不贴代码了。

上面讲完了类的加载机制,下面看下BootStrapClassLoader,ExtClassLoader,AppClassLoader的父子关系是何时建立起来的。

Launcher

java虚拟机在启动好之后会调用一个java类:sun.misc.Launcher,但这个类是不开源的,可以通过intellijidea等这类ide工具反编译出其源码。下面就通过阅读这个类来回答上面的问题。

publicclass Launcher {

/**这个就是我们熟知的AppClassLoader, 后面的代码中用会到**/

private ClassLoader loader;

public Launcher(){

       Launcher.ExtClassLoader var1;

       try{

            /**A:建立ExtClassLoader**/

            var1 = Launcher.ExtClassLoader.getExtClassLoader();

       }catch(IOException var10){

            thrownew InternalError("Could not create extension classloader", var10);

       }

 

       try{

            /**B:传入参数var1,也就是A步骤建立的ExtClassLoader,用于建立AppClassLoader**/

            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);

       }catch(IOException var9){

            thrownew InternalError("Could not create application classloader", var9);

  }

       /**

这行代码很重要,这行代码的作用是设置当前上下文加载器为AppClassLoader,这也就是为什么在我们的应用中获取类加载器的时候总是获取到AppClassLoader,这行代码是意义是变换了当前上下文类加载器,使顶层ClassLoader可以加载底层ClassLoader的类。

       */

       Thread.currentThread().setContextClassLoader(this.loader);

       /**...去掉了一些代码**/

   }

}

 

通过阅读Launcher类的构造函数,我们知道了java建立了两个类加载器,AppClassLoader,ExtClassLoader。并设置上下文加载器为AppClassLoader。上下文加载器突破java的类加载机制,用于解决顶层ClassLoader无法访问底层ClassLoader的类的问题。但对于他们的关系,以及他们能加载到的资源还未涉及到。

 

下面来看下ExtClassLoader是如何建立起来的。

 

ExtClassLoader是Launcher的静态内部类。父类是URLClassLoader。

staticclass ExtClassLoader extends URLClassLoader {

      publicstatic Launcher.ExtClassLoader getExtClassLoader()throws IOException{

/**C:var0定义了要从何处去加载资源。这是要从extdirs中去加载,extdirs后续会讲到。*/

            final File[] var0 = getExtDirs();

            try{

                return(Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction(){

                    public Launcher.ExtClassLoader run()throws IOException {

                        int var1 = var0.length;

                        for(int var2 =0; var2 < var1;++var2){

                            MetaIndex.registerDirectory(var0[var2]);

                        }

                        /**

                        D:调用了Launcher.ExtClassLoader,入参是var0,也就上C步骤定义的加载资源的地方

                        */

                        returnnew Launcher.ExtClassLoader(var0);

                    }

                });

            }catch(PrivilegedActionException var2){

                throw(IOException)var2.getException();

            }

       }

 

/**E:ExtClassLoader构造方法入参是D步骤的var0,也就是extdirs**/

public ExtClassLoader(File[] var1)throws IOException {

 /**

   此处调用了getExtURLs方法。getExtURLs方法把var1目录中的子目录或者文件转成url。此处调用了父类的构造器,ExtClassLoader的父类是URLClassLoader,看下URLClassLoader的构造方法里面做了什么。

   super调用了URLClassLoader的构造方法,但此处传入的parent加载器却是null,其实这个null就是BootstrapClassLoader,因BootstrapClassLoader为c++所写,所以用java是获取不到这个加载器的,也就是null。

 */

   super(getExtURLs(var1),(ClassLoader)null, Launcher.factory);

SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);

 /**...省去了一些代码**/ 

}
从代码中可以看出 ExtClassLoader 的父加载器为 null. 其实就是 BootStrapClassLoader 了,就好比 String classLoader null 一样。

ExtClassLoader加载器通过getExtDirs方法返回可以加载的资源路径。

privatestatic File[] getExtDirs(){

/**java.ext.dirs定义了ExtClassLoader加载资源的路径**/

   String var0 =System.getProperty("java.ext.dirs");

   File[] var1;

   if(var0 !=null){

       StringTokenizer var2 =new StringTokenizer(var0, File.pathSeparator);

       int var3 = var2.countTokens();

       var1 =new File[var3];

       for(int var4 =0; var4 < var3;++var4){

            var1[var4]=new File(var2.nextToken());

       }

   }else{

       var1 =new File[0];

   }

   return var1;

}

 

下面看AppClassLoader

staticclass AppClassLoader extends URLClassLoader {

   publicstatic ClassLoader getAppClassLoader(final ClassLoader var0)throws IOException {

       /**java.class.path系统属性定义了AppClassLoader的加载资源路径**/

       final String var1 = System.getProperty("java.class.path");

       final File[] var2 = var1 ==null?new File[0]:Launcher.getClassPath(var1);

       return(ClassLoader)AccessController.doPrivileged(new PrivilegedAction(){

            public Launcher.AppClassLoader run(){

                URL[] var1x = var1 ==null?new URL[0]:Launcher.pathToURLs(var2);

                /**

返回AppClassLoader,var1x 为加载路径的url

                    var0为父加载器

                */

                returnnew Launcher.AppClassLoader(var1x, var0);

            }

       });

   }

 

   AppClassLoader(URL[] var1, ClassLoader var2){

       /**

调用的super为URLClassLoader,通过前面对URLClassLoader

         的分析知道,var2为父加载器

       */

       super(var1, var2, Launcher.factory);

       this.ucp.initLookupCache(this);

}

 

通过分析Launcher的源码,可以看出AppClassLoader的父加载器为ExtClassLoader,AppClassLoader从java.class.path系统属性定义的路径中加载类和资源,java.class.path默认为当前路径,也就是”.”。可参考文档:https://docs.oracle.com/javase/8/docs/technotes/tools/windows/classpath.html。当我们用ide开发的时候,默认的类路径就是当前工程所在的文件夹,可那里并没有我们要加载的类,所以ide 会重写类路径。Eclipse会把类路径记录在.classpath文件中。Intellij  IDEA 会记录在.iml文件中。ExtClassLoader的父加载器为BootstrapClassLoader,从java.ext.dirs系统属性定义的路径中加载类和资源。

 


你可能感兴趣的:(JAVA)