Tomcat学习之ClassLoader
版权声明:本文为博主原创文章,未经博主允许不得转载。
类装载器
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 CLASSPATH
- rem Note that there are no quotes as we do not want to introduce random
- rem quotes into the CLASSPATH
- if "%CLASSPATH%" == "" goto emptyClasspath
- set "CLASSPATH=%CLASSPATH%;"
- :emptyClasspath
- set "CLASSPATH=%CLASSPATH%%CATALINA_HOME%\bin\bootstrap.jar"
- rem Add tomcat-juli.jar to classpath
- rem tomcat-juli.jar can be over-ridden per instance
- if not exist "%CATALINA_BASE%\bin\tomcat-juli.jar" goto juliClasspathHome
- set "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 path
- setCatalinaHome();
- 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 repository
- try {
- @SuppressWarnings("unused")
- URL url = new URL(repository);
- repositories.add(new Repository(repository, RepositoryType.URL));
- continue;
- } catch (MalformedURLException e) {
- // Ignore
- }
- // Local repository
- if (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/*.jar
- server.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 exists
- String classesPath = "/WEB-INF/classes";
- ...
- // Adding the repository to the class loader
- classLoader.addRepository(classesPath + "/", classRepository);
- // Setting up the JAR repository (/WEB-INF/lib), if it exists
- String libPath = "/WEB-INF/lib";
- ...
- // Looking up directory /WEB-INF/lib in the context
- NamingEnumeration<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学习—Tomcat的简介和目录以及配置文件介绍(Windows环境)
tomcat学习(8) 版权声明:本文为博主原创文章,未经博主允许不得转载. 今天学习TOMCAT,主要学习的是Tomcat的目录结构,配置文件! 1:Tomcat简介 Tomcat 服务器是一个免费 ...
- Tomcat 学习进阶历程之Tomcat架构与核心类分析
前面的http及socket两部分内容,主要是为了后面看Tomcat源代码而学习的一些网络基础.从这章開始.就開始实际深入到Tomcat的'内在'去看一看. 在分析Tomcat的源代码之前,准备先看一 ...
- Tomcat学习之Wrapper
Tomcat学习之Wrapper 分类: WEB服务器2012-08-30 22:16 1547人阅读 评论(0) 收藏 举报 tomcatservletwrapperservletslistexce ...
- Tomcat学习 HttpConnector和HttpProcessor启动流程和线程交互
一.tomat启动流程 1.启动HttpConnector connector等待连接请求,只负责接受socket请求,具体处理过程交给HttpProcessor处理. tomcat用户只能访问到co ...
- Tomcat 学习笔记二
学习一 java.bean.PropertyChangeListener用来监听bean类的属性值改变.当改变时同时执行对应事件.而且是线程安全的.tomcat用此reload的Boolean值改变是 ...
- Tomcat ----> 学习笔记
源码之几个常见类和接口的关系 在学习Servlet的时候经常见到以下几个合成单词和非合成单词:Servlet.GenericServlet.HttpServlet.它们之间有联系的.接下来我把它们的联 ...
- Tomcat学习总结(7)——Tomcat与Jetty比较
Jetty 基本架构 Jetty目前的是一个比较被看好的 Servlet 引擎,它的架构比较简单,也是一个可扩展性和非常灵活的应用服务器. 它有一个基本数据模型,这个数据模型就是 Handler(处理 ...
- Tomcat学习总结(3)——Tomcat优化详细教程
Tomcat是我们经常使用的 servlet容器之一,甚至很多线上产品都使用 Tomcat充当服务器.而且优化后的Tomcat性能提升显著,本文从以下几方面进行分析优化. 一.内存优化 默认情况下To ...
- tomcat学习步骤,附带打破双亲委派模型企业应用实战
1. tomcat入门 入门模块仅做学习大纲梳理,忽略了具体操作指引. Tomcat的三种部署模式: 简单架构模型 连接器的非阻塞模式(NIO) 通道(Channel).缓冲区(Buffer).选择器 ...
随机推荐
- 【HDU 1847】 Good Luck in CET-4 Everybody!
[题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=1847 [算法] 我们知道,每一种状态,要么必胜,要么必败 记忆化搜索即可 [代码] #includ ...
- css3 背景background
Css3背景<background> Css3包含多个新的背景属性,它们提供了对背景更强大的控制.可以自定义背景图的大小,可以规定背景图片的定位区域,css3还允许我们为元素使用多个背景图 ...
- 数据科学的完整学习路径(Python版)
转载自:http://python.jobbole.com/80981/ 英文(原文)连接:https://www.analyticsvidhya.com/learning-paths-data-sc ...
- Android WiFi热点完全研究(自定义创建、跳转系统界面设置、读取配置、切换,Android6.0适配)
前言: WiFi热点设置页面的安全性选项在Android 4.x上有“无”.“WPA PSK”.“WPA2 PSK”三个选项,在Android 5.0(含)之后去掉了WPA PSK选项(部分手机厂家会 ...
- 【Oracle】创建用户
任务: 1)创建用户siebel,密码oracle 2)授予sse_role,tblo_role角色 3)siebel用户没有对system,sysaux的使用权限 4)默认表空间ts_users,无 ...
- C#的split函数分割
C#的split函数分割 string str = textBox1.Text; string[] strlist = str.Split("\r\n".ToCharArray() ...
- form 表单的另类触发方式:报错触发
在用form表单提交的时候,遇到一个问题:表单未验证完,表单就提前提交了. 然后通过断点调试,发现form提交会因为函数报错提前提交. 即如果你的form提交过程中,没有执行到return true之 ...
- ZJOI2015 幻想乡战略游戏 动态点分治_树链剖分_未调完
Description 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来, ...
- C#学习 第九节
构造器 1.构造器(constructor)是类型的成员之一: 2.狭义的构造器是指“实例构造器”(instance constructor): 3.构造器的调用 student stu =new s ...
- css 垂直居中方法总结
工作中遇到垂直居中问题,特此总结了一下几种方式与大家分享.本文讨论的垂直居中仅支持IE8+ 1.使用绝对定位垂直居中 HTML <div class="container"& ...