Tomcat学习之ClassLoader
来源:互联网 发布:株洲县网络传销案 编辑:程序博客网 时间:2024/05/27 06:15
类装载器
JDK中提供了3种不同的类加载器:启动类装载器,扩展类装载器和系统类装载器。引导类装载器,用于引导启动JAVA虚拟机,当执行一个JAVA程序时,就会启动引导类装载器,它是使用本地代码来实现的,会装载%JAVA_HOME%\\jre\lib\rt.jar,它是所有类装载器类的父装载器。扩展类装载器负责载入标准扩展目录中的类,其搜索路径是%JAVA_HOME%\jre\lib\ext,只需要将打包好的jar文件放入这个目录就可以了,给开发提供了很大的便利性。系统类装载器是默认的装载器,其搜索路径是classpath。
JVM到底使用的是哪一个类装载器,取决于类装载器的代理模式。每当需要装载一个类的时候,会首先调用系统类装载器,但是系统类装载器并不会立即装载,而是将其交给父装载器:扩展类装载器,扩展类装载器其将交给引导类装载器,引导类装载器没有父装载器,它会尝试装载这个类,如果找不到这个类,会交给扩展类装载器,如果扩展类装载器还是没有找到,会交给系统类装载器,如果系统类装载器还是没有找到这个类,则会抛出java.lang.ClassNotFoundException异常。代理模式主要是为了解决类装载的安全问题。例如:对于自定类的java.lang.Object类,永远得不到装载,除非,rt.jar中确实没有这个类。
tomcat也提供了几种不同的类装载器用于加载不同位置的jar包和class文件,特别是Context容器需要有一个单独的类装载器,因为不同应用可能有相同的类,如果用同一个类装载器去装载,就不知道该加载哪个应用里面的类了。这些类装载器之间的关系如下图所示:
系统类装载器
tomcat的系统类装载器和JDK的系统类装载器有点不同的地方是搜索路径并不相同,在catalina.bat中做了如下修改:
rem Add on extra jar file to CLASSPATHrem Note that there are no quotes as we do not want to introduce randomrem quotes into the CLASSPATHif "%CLASSPATH%" == "" goto emptyClasspathset "CLASSPATH=%CLASSPATH%;":emptyClasspathset "CLASSPATH=%CLASSPATH%%CATALINA_HOME%\bin\bootstrap.jar"rem Add tomcat-juli.jar to classpathrem tomcat-juli.jar can be over-ridden per instanceif not exist "%CATALINA_BASE%\bin\tomcat-juli.jar" goto juliClasspathHomeset "CLASSPATH=%CLASSPATH%;%CATALINA_BASE%\bin\tomcat-juli.jar"先将classpath清空,因为classpath中可能有tomcat启动相关的类会影响tomcat的正常启动。然后将bootstrap.jar和tomcat-juli.jar加入classpath中,在catalina.bat中调用了Bootstrap类的main方法,这里系统类装载器会装载Bootstrap类,Bootstrap类用到的Catalina类也是由系统类装载器装载的。
public void init() throws Exception{// Set Catalina pathsetCatalinaHome();setCatalinaBase();initClassLoaders();Thread.currentThread().setContextClassLoader(catalinaLoader);SecurityClassLoad.securityClassLoad(catalinaLoader);...}private void initClassLoaders() {try {commonLoader = createClassLoader("common", null);if( commonLoader == null ) {// no config file, default to this loader - we might be in a 'single' env.commonLoader=this.getClass().getClassLoader();}catalinaLoader = createClassLoader("server", commonLoader);sharedLoader = createClassLoader("shared", commonLoader);} catch (Throwable t) {handleThrowable(t);log.error("Class loader creation threw exception", t);System.exit(1);}}
Common Class Loader
private ClassLoader createClassLoader(String name, ClassLoader parent)throws Exception {String value = CatalinaProperties.getProperty(name + ".loader");if ((value == null) || (value.equals("")))return parent;value = replace(value);List<Repository> repositories = new ArrayList<Repository>();StringTokenizer tokenizer = new StringTokenizer(value, ",");while (tokenizer.hasMoreElements()) {String repository = tokenizer.nextToken().trim();if (repository.length() == 0) {continue;}// Check for a JAR URL repositorytry {@SuppressWarnings("unused")URL url = new URL(repository);repositories.add(new Repository(repository, RepositoryType.URL));continue;} catch (MalformedURLException e) {// Ignore}// Local repositoryif (repository.endsWith("*.jar")) {repository = repository.substring(0, repository.length() - "*.jar".length());repositories.add(new Repository(repository, RepositoryType.GLOB));} else if (repository.endsWith(".jar")) {repositories.add(new Repository(repository, RepositoryType.JAR));} else {repositories.add(new Repository(repository, RepositoryType.DIR));}}ClassLoader classLoader = ClassLoaderFactory.createClassLoader(repositories, parent);...return classLoader;}(1)、从bootstrap.jar包可以找到catalina.properties文件:common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar,很明显这个类装载器搜索路径就是${catalina.home}/lib和${catalina.home}/lib/*.jar,之所以叫common class loader,是因为它加载每个应用要用到的公共jar包和class文件;
Catalina Class Loader
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jarserver.loader=shared.loader=common class loader是以common class loader为父装载器的,因此其搜索路径和common class loader一样。catalina class loader创建好后,在init方法中随即调用了Thread.currentThread().setContextClassLoader(catalinaLoader);将其设置为当前线程的类装载器
Shared Class Loader
Webapp Class Loader
tomcat的一个service除了容器和连接器外还有很多组件,比如sessionManager,logger,loader等,这个类装载器是以组件的形式附着在每个容器上的,Engine和Host的这两个容器的loader组件为null,context里面是有值的,看看context的startInternal方法:
protected synchronized void startInternal() throws LifecycleException {... if (getLoader() == null) { WebappLoader webappLoader = new WebappLoader(getParentClassLoader()); webappLoader.setDelegate(getDelegate()); setLoader(webappLoader); }... // Binding thread ClassLoader oldCCL = bindThread(); try { if (ok) { // Start our subordinate components, if any if ((loader != null) && (loader instanceof Lifecycle)) ((Lifecycle) loader).start(); // since the loader just started, the webapp classloader is now // created. // By calling unbindThread and bindThread in a row, we setup the // current Thread CCL to be the webapp classloader unbindThread(oldCCL); oldCCL = bindThread();}... } finally { // Unbinding thread unbindThread(oldCCL); }}很明显,context在启动的时候创建了一个loader组件,webapploader正是loader的实现类,这个类并不是最终的类装载器,在这个类里面有一个webappclassloader类型的字段叫classloader,这个classloader的创建是在loader组件的start方法中完成的
protected void startInternal() throws LifecycleException {... // Construct a class loader based on our current repositories list try { classLoader = createClassLoader(); classLoader.setResources(container.getResources()); classLoader.setDelegate(this.delegate); classLoader.setSearchExternalFirst(searchExternalFirst); if (container instanceof StandardContext) { classLoader.setAntiJARLocking( ((StandardContext) container).getAntiJARLocking()); classLoader.setClearReferencesStatic( ((StandardContext) container).getClearReferencesStatic()); classLoader.setClearReferencesStopThreads( ((StandardContext) container).getClearReferencesStopThreads()); classLoader.setClearReferencesStopTimerThreads( ((StandardContext) container).getClearReferencesStopTimerThreads()); classLoader.setClearReferencesHttpClientKeepAliveThread( ((StandardContext) container).getClearReferencesHttpClientKeepAliveThread()); } for (int i = 0; i < repositories.length; i++) { classLoader.addRepository(repositories[i]); } // Configure our repositories setRepositories(); setClassPath(); setPermissions(); ((Lifecycle) classLoader).start();... } catch (Throwable t) { ... }... }(1)、在createClassLoader方法中通过反射实例化了org.apache.catalina.loader.WebappClassLoader这个类,并调用了它的setParentClassLoader设置其父装载器为standardClassLoader
private WebappClassLoader createClassLoader() throws Exception { Class<?> clazz = Class.forName(loaderClass); WebappClassLoader classLoader = null; if (parentClassLoader == null) { parentClassLoader = container.getParentClassLoader(); } Class<?>[] argTypes = { ClassLoader.class }; Object[] args = { parentClassLoader }; Constructor<?> constr = clazz.getConstructor(argTypes); classLoader = (WebappClassLoader) constr.newInstance(args); return classLoader; }(2)、为webappclassloader添加仓库(仓库表示类装载器会在哪些路径搜索类)
private void setRepositories() throws IOException {...// Setting up the class repository (/WEB-INF/classes), if it existsString classesPath = "/WEB-INF/classes";...// Adding the repository to the class loaderclassLoader.addRepository(classesPath + "/", classRepository);// Setting up the JAR repository (/WEB-INF/lib), if it existsString libPath = "/WEB-INF/lib";...// Looking up directory /WEB-INF/lib in the contextNamingEnumeration<NameClassPair> enumeration = libDir.list("");while (enumeration.hasMoreElements()) {NameClassPair ncPair = enumeration.nextElement();String filename = libPath + "/" + ncPair.getName();if (!filename.endsWith(".jar"))continue;...try {JarFile jarFile = new JarFile(destFile);classLoader.addJar(filename, jarFile, destFile);} catch (Exception ex) { ...}...}}(3)、为类装载器设置权限,这里Globals.IS_SECURITY_ENABLED值为false,表示安全机制未打开,直接返回
protected static final String[] triggers = { "javax.servlet.Servlet", "javax.el.Expression" // Servlet API };
WebappClassLoader装载类
public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {...// 先检查本地缓存clazz = findLoadedClass0(name);if (clazz != null) {if (log.isDebugEnabled())log.debug(" Returning class from cache");if (resolve)resolveClass(clazz);return (clazz);}// 如果本地缓存没有,则检查上一级缓存clazz = findLoadedClass(name);if (clazz != null) {if (log.isDebugEnabled())log.debug(" Returning class from cache");if (resolve)resolveClass(clazz);return (clazz);}// 如果两个缓存都没有,则使用系统的类装载器进行装载,防止Web应用程序中的类覆盖J2EE的类try {clazz = system.loadClass(name);if (clazz != null) {if (resolve)resolveClass(clazz);return (clazz);}} catch (ClassNotFoundException e) {// Ignore}// 如果启用了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);// 若打开了delegateLoad标志位,调用父装载器来加载。如果父装载器为null,使用系统类装载器装载if (delegateLoad) {if (log.isDebugEnabled())log.debug(" Delegating to parent classloader1 " + parent);ClassLoader loader = parent;if (loader == null)loader = system;try {clazz = Class.forName(name, false, loader);if (clazz != null) {if (log.isDebugEnabled())log.debug(" Loading class from parent");if (resolve)resolveClass(clazz);return (clazz);}} catch (ClassNotFoundException e) {// Ignore}}// 从本地仓库中载入相关类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) {// Ignore}// 若当前仓库中没有需要的类,且delegateLoad标志位关闭,则使用父装载器。若父装载器为null,使用系统类装载器来装载if (!delegateLoad) {if (log.isDebugEnabled())log.debug(" Delegating to parent classloader at end: " + parent);ClassLoader loader = parent;if (loader == null)loader = system;try {clazz = Class.forName(name, false, loader);if (clazz != null) {if (log.isDebugEnabled())log.debug(" Loading class from parent");if (resolve)resolveClass(clazz);return (clazz);}} catch (ClassNotFoundException e) {// Ignore}}//仍未找到,抛出异常throw new ClassNotFoundException(name);}整个思路是:先到缓存中获取,如果缓存中有直接返回,否则根据delegateLoad采取不同的加载方式。如果未启用这个标志:先本地仓库加载再父装载器或者系统类装载器装载;如果启用了这个标志:直接由父装载器或者系统类装载器装载。
类缓存
tomcat之所以采用自定义类装载器,除了不同应用之间有相同类不好解决之外,还有一个原因是可以缓存类以提高速度。每个由webappclassloader装载的类被视为资源,用ResourceEntry表示。加入缓存的代码是在loadclass方法中完成的,前面提到会搜索本地仓库,就是在这步调用了findClass方法完成了类的查找,并把找到的类封装成ResourceEntry,最后把这个resourceEntry放入resourceEntries中缓存起来。- Tomcat学习之ClassLoader
- Tomcat学习之ClassLoader
- Tomcat学习之ClassLoader
- Tomcat研究之ClassLoader
- Tomcat研究之ClassLoader
- Tomcat研究之ClassLoader
- Tomcat 之 ClassLoader
- 通过Tomcat源码学习ClassLoader
- Tomcat 源代码分析之ClassLoader
- java学习之ClassLoader
- 【JVM学习】之 ClassLoader
- Tomcat ClassLoader
- tomcat classloader
- Tomcat ClassLoader
- Tomcat classloader
- ClassLoader与Tomcat的ClassLoader
- JVM学习之ClassLoader的工作机制
- JVM学习笔记一 之 ClassLoader
- 常用JS判断表单
- 金山快盘——大家来注册吧
- 【Access源码示例】模糊筛选子窗体\任意筛选子窗体数据\模糊查找子窗体数据
- Sql Server查询性能优化之走出索引的误区
- 产品logo的设计:图标与几何构成
- Tomcat学习之ClassLoader
- BloomFilter——大规模数据处理利器
- 一个操作系统的环境配置
- 使用资源文件实现C#国际化(Silverlight中英文的实现)
- 大白话解析模拟退火算法
- SCnetwork 和reachibility
- poj 1207 The 3n + 1 problem
- ubuntu下设置PATH环境变量
- 虚函数、多态、纯虚函数及接口