前言:
类加载机制,听起来“高大上”的东西,其实不是很难。理解类加载机制,对我们日常的开发和对tomcat的理解会有一个提升。
一 java的类加载机制。
1, java中类加载器的种类以及作用。
(1)bootstrap 启动类加载器 JVM实现的一部分,在JVM运行的时候就加载 JAVA的核心API(java.lang.* or java.io.*)
(2)ExtClassLoder 扩展类加载器 用来加载 java扩展的API 也就<JRE>/lib/ext 下面的类
(3)AppClassLoder 系统类加载器 加载CLASSPATH下面的类,也就是用户自己写的类
2, java类加载器的层次关系 和 调用顺序
加载顺序 :按照从上到下的顺序 加载 类
bootstrap 类加载器是由C++代码实现的 位于顶层
ExtClassLoder 类加载器 位于中层
AppClassLoder 类加载器 位于底层
注意:(ExtClassLoder 是 AppClassLoder 的父类 两者由java代码实现)
3, java类加载器的双亲委托模型
当一个类加载请求加载某个类的时候,首先检查是否已经加载过这个类,如果加载过直接返回。如果没有加载过,首先会委托父类加载器,父类加载器也会检查时候加载过这个类,如果还是没有则继续向上委托
如果最后的父类加载器不能加载这个类。则调用 bootstrap 去加载这个类,如果还是找不到则会抛出ClassNotFoundException。
这种模型的用途就是为了解决 类载入过程中的安全性问题。如果有恶意用户自定义了 一个类 如Object 类 去覆盖 java 关键类 则会有产生安全性问题。
4,加载器的命名空间
每个加载器都有自己的命名空间,命名空间由该加载器以及父类所有的加载器所加载的类组成,当加载器加载一个雷的时候
首先会去自己的命名空间中查找 是否 加载过这个类。
在同一命名空间不会出现出现类的完整名字完全相同的两个类,在不同的命名空间中有可能出现类的完整名字完全相同的两个类。
类加载器 | 命名空间 |
Loader1 | Class1 |
Loader2 | Class1Class2 |
Loader3 | Class1Class2Class3 |
Loader4 | Class1Class2Class3 |
5, 运行时包
由同一类加载器加载属于相同包的类,组成了运行时包。
决定两个类是否属于同一运行时包,不仅要看包名是否相同,还要看是否由,同一个ClassLoader加载。只有属于同一运行时包的类,才能互相访问包可见的类和成员。
这样的限制避免了用户自己的代码冒充核心类库的类访问核心类库包可见成员的情况。假设用户自己定义了一个类 java.lang.xxx ,并用自定义的 ClassLoader 装载,由于 Java.lang.* 和 java.lang.xxx 是由不同的装载器装载,属于不同的运行时包,所以 java.lang.xxx 不能访问核心类库 java.lang 中类的包可见成员。
二 Tomcat 类加载机制。
先上一张tomcat 5的类 tomcat类加载器的结构图,虽然东西老,但是原理相同。
Bootstrap
|
System
|
Common
/ \
Catalina Shared
/ ... ... \
webapp1 webappN
(1)Bootstrap:该加载器包含由JVM提供的基本的运行时类,加上放置在系统扩展目录(<JAVA_HOME/jre/lib/ext>)的JAR文件中的类。
注意——有些Java虚拟器(JVMs)也许把它作为不止一个类装载器用,或者作为一个类装载器用。
(2)System - 这个类装载器通常是以CLASSPATH环境变量的内容为基础来初始化的。在使用Tomcat作为Servlet/JSP容器的Web环境中,Tomcat在启动过程中清除了原有CLASSPATH的内容,并对其进行了重新定义。
-
$CATALINA_HOME/bin/bootstrap.jar - 包含用来初始化Tomcat 5服务器的main()方法,以及它所依赖的类装载器执行类。
-
$JAVA_HOME/lib/tools.jar - 包含用来把JSP页面转换成servlet类的"javac"编译器。
-
$CATALINA_HOME/bin/commons-logging-api.jar - Jakarta commons 记录应用程序界面。
-
$CATALINA_HOME/bin/commons-daemon.jar - Jakarta commons daemon API.
-
jmx.jar - JMX 1.2 执行。
(3)Common:该类加载器包含一些对Tomcat内部类和web应用可见的额外类。其中包括1jasper-compiler.jar:JSP 2.0编译器(2)jsp-api.jar:JSP 2.0 API(3)servlet-api.jar:servlet 2.4 API等等。
(4)Catalina:该加载器初始化用来包含实现Tomcat 5本身所需要所有类和资源;
(5)Shared:在所有的web应用程序间共享的类和资源;
(6)WebappN:为每个部署在单个Tomcat 5实例上的Web应用创建的类加载器。加载/WEB-INF/classes和WEB-INF/lib下的类和资源
tomcat 的类加载机制与java的类加载委派模型不同之处就是,当WebappN被请求记载一个类的时候,并不一定完全会按照 java类加载器的委托模式,WebappN首先会在本地库中查找而不是直接委托。但是也有例外
作为 JRE 一些基本类的一部分类不能被覆盖
下面附上tomcat WebappClassLoader 的load方法
public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { Class clazz = null; //首先检查已加载的类 // (0) Check our previously loaded local class cache clazz = findLoadedClass0(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Returning class from cache"); if (resolve) resolveClass(clazz); return (clazz); } // (0.1) Check our previously loaded class cache clazz = findLoadedClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Returning class from cache"); if (resolve) resolveClass(clazz); return (clazz); } // (0.2) Try loading the class with the system class loader, to prevent // the webapp from overriding J2SE classes try { clazz = system.loadClass(name); if (clazz != null) { if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { // Ignore } // (0.5) Permission to access this class when using a SecurityManager if (securityManager != null) { int i = name.lastIndexOf('.'); if (i >= 0) { try { securityManager.checkPackageAccess(name.substring(0,i)); } catch (SecurityException se) { String error = "Security Violation, attempt to use " + "Restricted Class: " + name; log.info(error, se); throw new ClassNotFoundException(error, se); } } } boolean delegateLoad = delegate || filter(name); //Tomcat允许按照配置来确定优先使用本Web应用的类加载器加载还是使用父类 //加载器来进行类加载,此处先使用父类加载器进行加载 // (1) Delegate to our parent if requested if (delegateLoad) { if (log.isDebugEnabled()) log.debug(" Delegating to parent classloader1 " + parent); ClassLoader loader = parent; if (loader == null) loader = system; try { clazz = loader.loadClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from parent"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } } //使用本地的类加载器进行加载 // (2) Search local repositories if (log.isDebugEnabled()) log.debug(" Searching local repositories"); try { clazz = findClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from local repository"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } //如果没有特殊配置的话,使用父类加载器加载类 // (3) Delegate to parent unconditionally if (!delegateLoad) { if (log.isDebugEnabled()) log.debug(" Delegating to parent classloader at end: " + parent); ClassLoader loader = parent; if (loader == null) loader = system; try { clazz = loader.loadClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from parent"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } } //若最终类还是没有找到,抛出异常 throw new ClassNotFoundException(name); }
从一个网络程序的角度来看,类和资源的装载以这样的顺序在下列贮藏室进行查找:
- 你的JVM的Bootstrap类
- 系统类装载器类(描述如上)
- 你的网络程序的/WEB-INF/classes
- 你的网络程序的/WEB-INF/lib/*.jar
- $CATALINA_HOME/common/classes
- $CATALINA_HOME/common/endorsed/*.jar
- $CATALINA_HOME/common/lib/*.jar
- $CATALINA_BASE/shared/classes
- $CATALINA_BASE/shared/lib/*.jar