JAVA之ClassLoader

Java的类加载器在sun.misc.Launcher中初始化。

    public Launcher() {
        ExtClassLoader localExtClassLoader;
        try {
          localExtClassLoader = ExtClassLoader.getExtClassLoader();
        }
        catch(IOException localIOException1) {
          throw new InternalError("Could not create extension class loader", localIOException1);
        }

        try {
          this.loader = AppClassLoader.getAppClassLoader(localExtClassLoader);
        }
        catch(IOException localIOException2) {
          throw new InternalError("Could not create application class loader", localIOException2);
        }

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

        String str = System.getProperty("java.security.manager");
        if(str != null) {
          SecurityManager localSecurityManager = null;
          if(( "".equals(str) ) || ( "default".equals(str) ))
            localSecurityManager = new SecurityManager();
          else
            try {
              localSecurityManager = (SecurityManager) this.loader.loadClass(str).newInstance();
            }
            catch(IllegalAccessException localIllegalAccessException) {}
            catch(InstantiationException localInstantiationException) {}
            catch(ClassNotFoundException localClassNotFoundException) {}
            catch(ClassCastException localClassCastException) {}
          if(localSecurityManager != null)
            System.setSecurityManager(localSecurityManager);
          else
            throw new InternalError("Could not create SecurityManager: " + str);
        }
    }

ExtClassLoader通过ExtClassLoader.getExtClassLoader()初始化。
AppClassLoader通过AppClassLoader.getAppClassLoader(ExtClassLoader)初始化。

    public static ExtClassLoader getExtClassLoader() throws IOException {
      File[] arrayOfFile = getExtDirs();
      try {
        return( (ExtClassLoader) AccessController.doPrivileged(new PrivilegedExceptionAction(arrayOfFile) {
          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]);
            }
            return new Launcher.ExtClassLoader(this.val$dirs);
          }
        }) );
      }
      catch(PrivilegedActionException localPrivilegedActionException) {
        throw( (IOException) localPrivilegedActionException.getException() );
      }
    }

getExtDirs找到系统配置System.getProperty("java.ext.dirs")中的路径下的所有文件,
让类加载器加载这些文件。

    public ExtClassLoader( File[] paramArrayOfFile ) throws IOException {
      super(getExtURLs(paramArrayOfFile), null, Launcher.factory);
      SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
    }

ExtClassLoader初始化时将parent设置为null。

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

      return( (ClassLoader) AccessController.doPrivileged(new PrivilegedAction(str, arrayOfFile, paramClassLoader) {
        public Launcher.AppClassLoader run() {
          URL[] arrayOfURL = ( this.val$s == null ) ? new URL[0] : Launcher.access$300(this.val$path);

          return new Launcher.AppClassLoader(arrayOfURL, this.val$extcl);
        }
      }) );
    }

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

getAppClassLoader从System.getProperty("java.class.path")处获取要加载的文件,并在初始化时将类加载器的parent设置为ExtClassLoader。

类加载过程

    protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    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
                }

                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) {
                resolveClass(c);
            }
            return c;
        }
    }

该函数逻辑为:

    if 该类已经被当前加载器加载过
        return;
    else if 当前类加载器存在parent加载器
        尝试是否parent加载器加载
    else if 不存在parent加载器器
        尝试使用BootstrapClassLoader加载器加载

    if 父加载器无法加载该类
        使用当前加载器加载。

因为AppClassLoader的parent为ExtClassLoader,而为ExtClassLoader的parent为null。依据上面的类加载过程,整个类的加载过程为

JAVA之ClassLoader_第1张图片
类加载过程

这种类加载的模式即为双亲委托模式,简单一句话描述就是:
类优先让父加载器加载,父加载器无法加载后才会自己加载。

委托机制的意义
防止内存中出现多份同样的字节码

比如两个类A和类B都要加载System类:
如果不用委托而是自己加载自己的,那么加载器A就会加载一份System字节码,然后加载器B又会加载一份System字节码,这样内存中就出现了两份System字节码。
如果使用委托机制,会递归的向父类查找,也就是首选用Bootstrap尝试加载,如果找不到再向下。这里的System就能在Bootstrap中找到然后加载,如果此时类B也要加载System,也从Bootstrap开始,此时Bootstrap发现已经加载过了System那么直接返回内存中的System即可而不需要重新加载,这样内存中就只有一份System的字节码了。

当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?

  • 首先当前线程的类加载器去加载线程中的第一个类(假设为类A)。
    注:当前线程的类加载器可以通过Thread类的getContextClassLoader()获得,也可以通过setContextClassLoader()自己设置类加载器。
  • 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器去加载类B。
  • 还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

违背双亲委派模式
当然双亲委派模式并不是强制要求,在有些情况下会打破双亲委派模式。
如Tomcat加载类过程。

JAVA之ClassLoader_第2张图片
Tomcat类加载体系

commonLoader:类库可被Tomcat和所有的Web应用程序共同使用。
catalinaLoader:类库可被Tomcat使用,对所有的Web应用程序都不可见。
sharedLoader:类库可被所有的Web应用程序共同使用,但对Tomcat自己不可见。
webappclassLoader:每个Web应用程序单独使用,对其他web应用不可见。

如果有10个Web应用程序都是用Spring来进行组织和管理的话,可以把Spring放到Common或Shared目录下让这些程序共享。Spring要对用户程序的类进行管理,自然要能访问到用户程序的类,而用户的程序显然是放在/WebApp/WEB-INF目录中的,那么被CommonClassLoader或SharedClassLoader加载的Spring如何访问并不在其加载范围内的用户程序呢?

因为Spring的jar包在common或shared目录中,所以Spring的类加载器为commonLoader或者sharedLoader。而Spring中的注入的bean是在放在/WebApp/WEB-INF目录中的,根据 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器去加载类B,在Spring中引用web应用的类时,需要commonLoader或者sharedLoader加载去加载,而commonLoader或sharedLoader是无法加载到/WebApp/WEB-INF下的类的,这时候就需要commonLoader或者sharedLoader加载器来调用子加载器webappclassLoader来加载/WebApp/WEB-INF目录下的类,在实现时是使用线程上下文类加载器,可以实现父加载器对子加载器的逆向访问。

热部署

重新加载
ClassLoader中重要的方法
loadClass
ClassLoader.loadClass(...) 是ClassLoader的入口点。当一个类没有指明用什么加载器加载的时候,JVM默认采用AppClassLoader加载器加载没有加载过的class,调用的方法的入口就是loadClass(...)。如果一个class被自定义的ClassLoader加载,那么JVM也会调用这个自定义的ClassLoader.loadClass(...)方法来加载class内部引用的一些别的class文件。重载这个方法,能实现自定义加载class的方式,抛弃双亲委托机制,但是即使不采用双亲委托机制,比如java.lang包中的相关类还是不能自定义一个同名的类来代替,主要因为JVM解析、验证class的时候,会进行相关判断。

defineClass
系统自带的ClassLoader,默认加载程序的是AppClassLoader,ClassLoader加载一个class,最终调用的是defineClass(...)方法,这时候就在想是否可以重复调用defineClass(...)方法加载同一个类(或者修改过),最后发现调用多次的话会有相关错误:
...
java.lang.LinkageError
attempted duplicate class definition
...
所以一个class被一个ClassLoader实例加载过的话,就不能再被这个ClassLoader实例再次加载(这里的加载指的是,调用了defileClass(...)放方法,重新加载字节码、解析、验证。)。而系统默认的AppClassLoader加载器,他们内部会缓存加载过的class,重新加载的话,就直接取缓存。所与对于热加载的话,只能重新创建一个ClassLoader,然后再去加载已经被加载过的class文件。

class卸载
在Java中class也是可以unload。JVM中class和Meta信息存放在PermGen space区域。如果加载的class文件很多,那么可能导致PermGen space区域空间溢出。引起:java.lang.OutOfMemoryErrorPermGen space. 对于有些Class我们可能只需要使用一次,就不再需要了,也可能我们修改了class文件,我们需要重新加载 newclass,那么oldclass就不再需要了。那么JVM怎么样才能卸载Class呢。

JVM中的Class只有满足以下三个条件,才能被GC回收,也就是该Class被卸载(unload):

  • 该类所有的实例都已经被GC。
  • 加载该类的ClassLoader实例已经被GC。
  • 该类的java.lang.Class对象没有在任何地方被引用。

GC的时机我们是不可控的,那么同样的我们对于Class的卸载也是不可控的。

你可能感兴趣的:(JAVA之ClassLoader)