美文网首页
JAVA之ClassLoader

JAVA之ClassLoader

作者: LordZhou | 来源:发表于2017-01-18 10:14 被阅读0次

    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。依据上面的类加载过程,整个类的加载过程为

    类加载过程

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

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

    比如两个类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加载类过程。

    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

          本文链接:https://www.haomeiwen.com/subject/leombttx.html