JVM(六)虚拟机类加载委派模型

双亲委派模型

当一个类装载器(class loader)被请求装载类时,它首先按照顺序在上层装载器、父装载器以及自身的装载器的缓存里检查这个类是否已经存在。简单来说,就是在缓存里查看这个类是否已经被自己装载过了,如果没有的话,继续查找父类的缓存,直到在bootstrap类装载器里也没有找到的话,它就会自己在文件系统里去查找并且加载这个类。

ClassLoader的loadClass方法

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;
    }
}

1)先找缓存(findLoadedClass),没有的话就判断有没有parent,有的话就用parent来递归的loadClass,然而ExtClassLoader并没有设置parent,则会通过findBootstrapClassOrNull来加载class,而findBootstrapClassOrNull则会通过JNI方法“private native Class findBootstrapClass(String name)“来使用BootStrapClassLoader来加载class

2)然后如果parent未找到class,则会调用findClass来加载classfindClass是一个protected的空方法,可以覆盖它以便自定义class加载过程。另外,虽然ClassLoader加载类是使用loadClass方法,但是鼓励用 ClassLoader 的子类重写 findClass(String),而不是重写loadClass,这样就不会覆盖了类加载默认的双亲委派机制。

双亲委派模型有一个缺陷,如果父ClassLoader想加载子ClassLoader中的类比较困难,而在有的应用中这种加载方式是需要的,比如JNDI,Servlet等

破坏双亲委派模型

1) JDK1.2 java.lang.ClassLoader提供新的protected方法findClass()public方法loadClass(),来实现自定义类加载和破坏委派模型。

2)JDK1.3使用线程上下文(ThreadContextClassLoader)解决让父类加载器请求子类完成类的加载,例如JNDIJDBCJCEJAXB,以及应用服务器Tocmat

3)OSGi,代码热替换(HotSwap),代码热部署(HotDeployment

关于JDBCSPI加载,可参照ServiceLoader深入解析,本文主要讲解下jetty的类加载机制。

Jetty的ClassLoader体系结构如下所示:

Jetty 有两种运行方式,一种进程内运行(通过反射执行MainClass),一种是进程外执行 ( 通过 Runtime.getRuntime().exec()), 进程外执行时由于线程上下文不能进程之间传递。

Jetty至少要保证其内部运行的多个webapp之间私有的类库不受影响,并且公有的类库可以共享。Jetty中有一个org.mortbay.jetty.webapp.WebAppClassLoader,负责加载一个webapp context中的应用类,WebAppClassLoader以系统类加载器作为parent,用于加载系统类。不过servlet规范使得web容器的ClassLoader比正常的ClassLoader委托模型稍稍复杂,Servlet规范要求:

1)WEB-INF/libWEB-INF/classes优先于父容器中的类加载,比如WEB-INF/classes下有个XYZ类,classpath下也有个XYZ类,jetty中优先加载的是WEB-INF/classes下的,这与正常的父加载器优先相反(child-first)

2)系统类比如java.lang.String不遵循第一条,WEB-INF/classesWEB-INF/lib下的类不能替换系统类。不过规范中没有明确规定哪些是系统类,jetty中的实现是按照类的全路径名判断。

3)Server的实现类不被应用中的类引用,即Server的实现类不能被任何应用类加载器加载。不过,同样的,规范里没有明确规定哪些是Server的实现类,jetty中同样是按照类的全路径名判断。

为了处理上述三个问题,jetty的应用类加载器(org.mortbay.jetty.webapp.WebAppClassLoader)做了些特殊处理。

public WebAppClassLoader(ClassLoader parent, Context context)
       throws IOException {
       super(new URL[]{},parent!=null?parent
                     :(Thread.currentThread().getContextClassLoader()!=null?Thread.currentThread().getContextClassLoader()
                                   :(WebAppClassLoader.class.getClassLoader()!=null?WebAppClassLoader.class.getClassLoader()
                                                 :ClassLoader.getSystemClassLoader())));
       _parent=getParent();
       _context=context;
       if (_parent==null)
              throw new IllegalArgumentException("no parent classloader!");
      
       _extensions.add(".jar");
       _extensions.add(".zip");
      
       // TODO remove this system property
       String extensions = System.getProperty(WebAppClassLoader.class.getName() + ".extensions");
       if(extensions!=null)
       {
              StringTokenizer tokenizer = new StringTokenizer(extensions, ",;");
              while(tokenizer.hasMoreTokens())
                     _extensions.add(tokenizer.nextToken().trim());
       }
       if (context.getExtraClasspath()!=null)
              addClassPath(context.getExtraClasspath());
}

他是以当前线程上下文的 ClassLoader 为父 classloader, 如果上下文没有设定 classLoader ,就使用加载 WebAppClassLoader 的加载器,如果还是没有,则采用系统类加载器。很明显,默认情况下,如果采用进程内运行,那么这个 parent 就是 Loader( 系统自定义类加载器 ) ,如果是进程外, parent 就是系统类加载器。 

WebAppClassLoader 可以设定是否由 parent 优先加载 lib/ classes 下的类。

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
       Class<?> c= findLoadedClass(name);
       ClassNotFoundException ex= null;
       boolean tried_parent= false;
      
       boolean system_class=_context.isSystemClass(name);
       boolean server_class=_context.isServerClass(name);
      
       if (system_class && server_class)
       {
              return null;
       }
      
       if (c == null && _parent!=null && (_context.isParentLoaderPriority() || system_class) && !server_class)
       {
              tried_parent= true;
              try
              {
                     c= _parent.loadClass(name);
                     if (LOG.isDebugEnabled())
                            LOG.debug("loaded " + c);
              }
              catch (ClassNotFoundException e)
              {
                     ex= e;
              }
       }
 
       if (c == null)
       {
              try
              {
                     c= this.findClass(name);
              }
              catch (ClassNotFoundException e)
              {
                     ex= e;
              }
       }
 
       if (c == null && _parent!=null && !tried_parent && !server_class )
              c= _parent.loadClass(name);
 
       if (c == null && ex!=null)
              throw ex;
 
       if (resolve)
              resolveClass(c);
 
       if (LOG.isDebugEnabled())
              LOG.debug("loaded {} from {}",c,c==null?null:c.getClassLoader());
      
       return c;
}

 findLoadedClass(name)-检查类是否已经加载

1)判断该类是否为系统类或server

2如果该类未加载且父加载器不为空且设置了父加载器优先或类类为系统类,且该类不是server类,则尝试使用父加载器加载该类

3如果不是父加载器优先或者父加载器未加载到该类,使用WebAppClassLoader加载该类

4如果是不是父加载器优先,并且WebAppClassLoader未加载到该类,尝试使用父加载器加载该类

5找到则返回,否则抛出ClassNotFoundException

再看下findClass方法

protected Class<?> findClass(final String name) throws ClassNotFoundException {
       Class<?> clazz=null;
 
       if (_transformers.isEmpty())
              clazz = super.findClass(name);
       else
       {
              String path = name.replace('.', '/').concat(".class");
              URL url = getResource(path);
              if (url==null)
                     throw new ClassNotFoundException(name);
 
              InputStream content=null;
              try
              {
                     content = url.openStream();
                     byte[] bytes = IO.readBytes(content);
                           
                     for (ClassFileTransformer transformer : _transformers)
                     {
                            byte[] tmp = transformer.transform(this,name,null,null,bytes);
                            if (tmp != null)
                                   bytes = tmp;
                     }
                    
                     clazz=defineClass(name,bytes,0,bytes.length);
              }
              catch (IOException e)
              {
                     throw new ClassNotFoundException(name,e);
              }
              catch (IllegalClassFormatException e)
              {
                     throw new ClassNotFoundException(name,e);
              }
              finally
              {
                     if (content!=null)
                     {
                            try
                            {
                                   content.close();
                            }
                            catch (IOException e)
                            {
                                   throw new ClassNotFoundException(name,e);
                            }
                     }
              }
       }
 
       return clazz;
}

_transformers为空调用父类的findClass,不为空找到对应的类文件对其进行转换。 

1、设置ClassLoader Priority

上述过程涉及一个加载器优先级的概念,这也是针对前述第一条规范中WEB-INF/libWEB-INF/classes类优先的处理。jetty中父加载器优先的配置项可以通过环境变量

org.eclipse.jetty.server.webapp.parentLoaderPriority=false(默认)/true来设置

也可以通过

org.eclipse.jetty.webapp.WebAppContext.setParentLoaderPriority(boolean)方法来设置

优于该配置默认是false,因此在load class过程中优先使用WebAppClassLoader加载WEB-INF/libWEB-INF/classes中的类。当将该配置项设为true时需要确认类加载顺序没有问题

2、设置系统类

规范2中约定系统类不能被应用类覆盖,但是没有明确规定哪些时系统类,jetty中以类的package路径名来区分,当类的package路径名位包含于

// System classes are classes that cannot be replaced by
// the web application, and they are *always* loaded via
// system classloader.
public final static String[] __dftSystemClasses =
{
       "java.",                            // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
       "javax.",                           // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
       "org.xml.",                         // needed by javax.xml
       "org.w3c.",                         // needed by javax.xml
       "org.eclipse.jetty.jmx.",           // webapp cannot change jmx classes
       "org.eclipse.jetty.util.annotation.",  // webapp cannot change jmx annotations
       "org.eclipse.jetty.continuation.",  // webapp cannot change continuation classes
       "org.eclipse.jetty.jndi.",          // webapp cannot change naming classes
       "org.eclipse.jetty.jaas.",          // webapp cannot change jaas classes
       "org.eclipse.jetty.websocket.",     // webapp cannot change / replace websocket classes
       "org.eclipse.jetty.util.log.",      // webapp should use server log
       "org.eclipse.jetty.servlet.ServletContextHandler.Decorator", // for CDI / weld use
       "org.eclipse.jetty.servlet.DefaultServlet", // webapp cannot change default servlets
       "org.eclipse.jetty.jsp.JettyJspServlet", //webapp cannot change jetty jsp servlet
       "org.eclipse.jetty.servlets.AsyncGzipFilter" // special case for AsyncGzipFilter
} ;

时,会被认为是系统类。(该定义位于WebAppContext中)

因此,我们可以通过 org.eclipse.jetty.webapp.WebAppContext.setSystemClasses(String Array)或者org.eclipse.jetty.webapp.WebAppContext.addSystemClass(String)来设置系统类。
再次提醒,系统类是对多有应用都可见。

3、设置Server

规范3中约定Server类不对任何应用可见。jetty同样是用package路径名来区分哪些是Server类。Server类包括:

// Server classes are classes that are hidden from being
// loaded by the web application using system classloader,
// so if web application needs to load any of such classes,
// it has to include them in its distribution.
public final static String[] __dftServerClasses =
{
       "-org.eclipse.jetty.jmx.",          // don't hide jmx classes
       "-org.eclipse.jetty.util.annotation.", // don't hide jmx annotation
       "-org.eclipse.jetty.continuation.", // don't hide continuation classes
       "-org.eclipse.jetty.jndi.",         // don't hide naming classes
       "-org.eclipse.jetty.jaas.",         // don't hide jaas classes
       "-org.eclipse.jetty.servlets.",     // don't hide jetty servlets
       "-org.eclipse.jetty.servlet.DefaultServlet", // don't hide default servlet
       "-org.eclipse.jetty.jsp.",          //don't hide jsp servlet
       "-org.eclipse.jetty.servlet.listener.", // don't hide useful listeners
       "-org.eclipse.jetty.websocket.",    // don't hide websocket classes from webapps (allow webapp to use ones from system classloader)
       "-org.eclipse.jetty.apache.",       // don't hide jetty apache impls
       "-org.eclipse.jetty.util.log.",     // don't hide server log
       "-org.eclipse.jetty.servlet.ServletContextHandler.Decorator", // don't hide CDI / weld interface 
       "org.objectweb.asm.",               // hide asm used by jetty
       "org.eclipse.jdt.",                 // hide jdt used by jetty
       "org.eclipse.jetty."                // hide other jetty classes
} ;

 我们可以通过, org.eclipse.jetty.webapp.WebAppContext.setServerClasses(String Array) org.eclipse.jetty.webapp.WebAppContext.addServerClass(String)方法设置Server类。 注意,Server类是对所有应用都不可见的,但是WEB-INF/lib下的类可以替换Server类。

当默认的WebAppClassLoader不能满足需求时,可以自定义WebApp ClassLoader,不过jetty建议自定义的classloader要扩展于默认的WebAppClassLoader实现。