- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
众所周知,Java中默认的类加载器是以父子关系存在的,实现了双亲委派机制进行类的加载,在前文中,我们提到了,双亲委派机制的设计是为了保证类的唯一性,这意味着在同一个JVM中是不能加载相同类库的不同版本的类。 然而与许多服务器应用程序一样,Tomcat 允许容器的不同部分以及在容器上运行的不同Web应用程序可以访问的各种不同版本的类库,这就要求Tomcat必须打破这种双亲委派机制,通过实现自定义的类加载器(即实现了java.lang.ClassLoader)进行类的加载。下面,就让我们来看看Tomcat类加载原理是怎样的.
Tomcat中有两个最重要的类加载器,第一个便是负责Web应用程序类加载的WebappClassLoader,另一个便是JSP Servlet类加载器`JasperLoader.
上代码:
public class WebappClassLoader extends WebappClassLoaderBase { public WebappClassLoader( ) { super(); } public WebappClassLoader( ClassLoader parent) { super(parent); } ... }
我们来看看WebappClassLoader继承的WebappClassLoaderBase中实现的类加载方法loadClass 。
public abstract class WebappClassLoaderBase extends URLClassLoader implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck { ... 省略不需要关注的代码 protected WebappClassLoaderBase() { super( new URL[ 0]); // 获取当前WebappClassLoader的父加载器系统类加载器 ClassLoader p = getParent(); if (p = = null) { p = getSystemClassLoader(); } this.parent = p; // javaseClassLoader变量经过以下代码的执行, // 得到的是扩展类加载器(ExtClassLoader) ClassLoader j = String.class.getClassLoader(); if (j = = null) { j = getSystemClassLoader(); while (j.getParent() ! = null) { j = j.getParent(); } } this.javaseClassLoader = j; securityManager = System.getSecurityManager(); if (securityManager ! = null) { refreshPolicy(); } } ...省略不需要关注的代码 @Override public Class <? > loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { if (log.isDebugEnabled()) { log.debug( "loadClass(" + name + ", " + resolve + ")"); } Class <? > clazz = null; // Web应用程序停止状态时,不允许加载新的类 checkStateForClassLoading(name); // 如果之前加载过该类,就可以从Web应用程序类加载器本地类缓存中查找, // 如果找到说明WebappClassLoader之前已经加载过这个类 clazz = findLoadedClass0(name); if (clazz ! = null) { if (log.isDebugEnabled()) { log.debug( " Returning class from cache"); } if (resolve) { resolveClass(clazz); } return clazz; } // Web应用程序本地类缓存中没有,可以从系统类加载器缓存中查找, // 如果找到说明AppClassLoader之前已经加载过这个类 clazz = findLoadedClass(name); if (clazz ! = null) { if (log.isDebugEnabled()) { log.debug( " Returning class from cache"); } if (resolve) { resolveClass(clazz); } return clazz; } // 将类似java.lang.String这样的类名这样转换成java/lang/String // 这样的资源文件名 String resourceName = binaryNameToPath(name, false); // 获取引导类加载器(BootstrapClassLoader) ClassLoader javaseLoader = getJavaseClassLoader(); boolean tryLoadingFromJavaseLoader; try { // 引导类加载器根据转换后的类名获取资源url,如果url不为空,就说明找到要加载的类 URL url; if (securityManager ! = null) { PrivilegedAction <URL > dp = new PrivilegedJavaseGetResource(resourceName); url = AccessController.doPrivileged(dp); } else { url = javaseLoader.getResource(resourceName); } tryLoadingFromJavaseLoader = (url ! = null); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); tryLoadingFromJavaseLoader = true; } // 首先,从扩展类加载器(ExtClassLoader)加载,防止Java核心API库被Web应用程序类随意篡改 if (tryLoadingFromJavaseLoader) { try { clazz = javaseLoader.loadClass(name); if (clazz ! = null) { if (resolve) { resolveClass(clazz); } return clazz; } } catch (ClassNotFoundException e) { // Ignore } } // 当使用安全管理器时,允许访问这个类 if (securityManager ! = null) { int i = name.lastIndexOf( '.'); if (i > = 0) { try { securityManager.checkPackageAccess(name.substring( 0,i)); } catch (SecurityException se) { String error = sm. getString( "webappClassLoader.restrictedPackage", name); log.info( error, se); throw new ClassNotFoundException( error, se); } } } /* * 如果Web应用程序类加载器配置为,<Loader delegate="true"/> 或者满足下列条件的类: * 当前类属于以下这些jar包中: * annotations-api.jar — Common Annotations 1.2 类。 * catalina.jar — Tomcat 的 Catalina servlet 容器部分的实现。 * catalina-ant.jar — 可选。用于使用 Manager Web 应用程序的 Tomcat Catalina Ant 任务。 * catalina-ha.jar — 可选。提供基于 Tribes 构建的会话集群功能的高可用性包。 * catalina-storeconfig.jar — 可选。从当前状态生成 XML 配置文件。 * catalina-tribes.jar — 可选。高可用性包使用的组通信包。 * ecj-*.jar — 可选。Eclipse JDT Java 编译器用于将 JSP 编译为 Servlet。 * el-api.jar — 可选。EL 3.0 API。 * jasper.jar — 可选。Tomcat Jasper JSP 编译器和运行时。 * jasper-el.jar — 可选。Tomcat EL 实现。 * jaspic-api.jar — JASPIC 1.1 API。 * jsp-api.jar — 可选。JSP 2.3 API。 * servlet-api.jar — Java Servlet 3.1 API。 * tomcat-api.jar — Tomcat 定义的几个接口。 * tomcat-coyote.jar — Tomcat 连接器和实用程序类。 * tomcat-dbcp.jar — 可选。基于 Apache Commons Pool 2 和 Apache Commons DBCP 2 的 * 包重命名副本的数据库连接池实现。 * tomcat-i18n-**.jar — 包含其他语言资源包的可选 JAR。由于默认包也包含在每个单独的JAR * 中,如果不需要消息国际化,可以安全地删除它们。 * tomcat-jdbc.jar — 可选。另一种数据库连接池实现,称为 Tomcat JDBC 池。有关详细信息,请参阅 文档。 * tomcat-jni.jar — 提供与 Tomcat Native 库的集成。 * tomcat-util.jar — Apache Tomcat 的各种组件使用的通用类。 * tomcat-util-scan.jar — 提供 Tomcat 使用的类扫描功能。 * tomcat-websocket.jar — 可选。Java WebSocket 1.1 实现 * websocket-api.jar — 可选。Java WebSocket 1.1 API * * 此处的filter方法,实际上tomcat官方将filter类加载过滤条件,看作是一种类加载器, * 将其取名为CommonClassLoader */ boolean delegateLoad = delegate | | filter(name, true); // 如果ExtClassLoader没有获取到,说明是非JRE核心类,那么就从系统类加载器(也称AppClassLoader // 应用程序类加载器)加载 if (delegateLoad) { if (log.isDebugEnabled()) { log.debug( " Delegating to parent classloader1 " + parent); } try { clazz = Class.forName(name, false, parent); if (clazz ! = null) { if (log.isDebugEnabled()) { log.debug( " Loading class from parent"); } if (resolve) { resolveClass(clazz); } return clazz; } } catch (ClassNotFoundException e) { // Ignore } } // 从Web应用程序的类加载器(也就是WebappClassLoader)中加载类。Web应用程序的类加载器是 // 一个特殊的类加载器,它负责从Web应用程序的本地库中加载类 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 } // 经过上面几个步骤还未加载到类,则采用系统类加载器(也称应用程序类加载器)进行加载 if ( !delegateLoad) { if (log.isDebugEnabled()) { log.debug( " Delegating to parent classloader at end: " + parent); } try { clazz = Class.forName(name, false, parent); 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); } ...省略不需要关注的代码 }
综上所述,我们得出WebappClassLoader类加载器打破了双亲委派机制,自定义类加载类的顺序:
如果Web应用程序类加载器配置为,,也就是WebappClassLoaderBase类的变量delegate=true时,则类加载顺序变为:
上代码:
public class JasperLoader extends URLClassLoader { private final PermissionCollection permissionCollection; private final SecurityManager securityManager; // JSP类加载器的父加载器是Web应用程序类加载器(WebappClassLoader) public JasperLoader(URL[] urls, ClassLoader parent, PermissionCollection permissionCollection) { super(urls, parent); this.permissionCollection = permissionCollection; this.securityManager = System.getSecurityManager(); } @Override public Class <? > loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } @Override public synchronized Class <? > loadClass(final String name, boolean resolve) throws ClassNotFoundException { Class <? > clazz = null; // 从JVM的类缓存中查找 clazz = findLoadedClass(name); if (clazz ! = null) { if (resolve) { resolveClass(clazz); } return clazz; } // 当使用SecurityManager安全管理器时,允许访问访类 if (securityManager ! = null) { int dot = name.lastIndexOf( '.'); if (dot > = 0) { try { // Do not call the security manager since by default, we grant that package. if ( ! "org.apache.jasper.runtime".equalsIgnoreCase(name.substring( 0,dot))){ securityManager.checkPackageAccess(name.substring( 0,dot)); } } catch (SecurityException se) { String error = " Security Violation, attempt to use " + " Restricted Class: " + name; se.printStackTrace(); throw new ClassNotFoundException( error); } } } // 如果类名不是以org.apache.jsp包名开头的,则采用WebappClassLoader加载 if( !name.startsWith(Constants.JSP_PACKAGE_NAME + '.') ) { // Class is not in org.apache.jsp, therefore, have our // parent load it clazz = getParent().loadClass(name); if( resolve ) { resolveClass(clazz); } return clazz; } // 如果是org.apache.jsp包名开头JSP类,就调用父类URLClassLoader的findClass方法 // 动态加载类文件,解析成Class类,返回给调用方 return findClass(name); } }
下面是URLClassLoader的findClass方法,具体实现:
protected Class
<? > findClass(final String name) throws ClassNotFoundException { final Class <? > result; try { result = AccessController.doPrivileged( new PrivilegedExceptionAction <Class <? > >() { public Class <? > run() throws ClassNotFoundException { String path = name.replace( '.', '/').concat( ".class"); Resource res = ucp.getResource(path, false); if (res ! = null) { try { // 解析类的字节码文件生成Class类对象 return defineClass(name, res); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } else { return null; } } }, acc); } catch (java.security.PrivilegedActionException pae) { throw (ClassNotFoundException) pae.getException(); } if (result = = null) { throw new ClassNotFoundException(name); } return result; }
从源码中我们可以看到,JSP类加载原理是,先从JVM类缓存中(也就是Bootstrap类加载器加载的类)加载,如果不是核心类库的类,就从Web应用程序类加载器WebappClassLoader中加载,如果还未找到,就说明是jsp类,则通过动态解析jsp类文件获得要加载的类.
经过上面两个Tomcat核心类加载器的剖析,我们也就知道了Tomcat类的加载原理了。 下面我们来总结一下: Tomcat会为每个Web应用程序创建一个WebappClassLoader类加载器进行类的加载,不同的类加载器实例加载的类是会被认为是不同的类,即使它们的类名相同,这样的话就可以实现在同一个JVM下,允许Tomcat容器的不同部分以及在容器上运行的不同Web应用程序可以访问的各种不同版本的类库。 针对JSP类,会由专门的JSP类加载器(JasperLoader)进行加载,该加载器会针对JSP类在每次加载时都会解析类文件,Tomcat容器会启动一个后台线程,定时检测JSP类文件的变化,及时更新类文件,这样就实现JSP文件的热加载功能.
如果您觉得博文不错,请用微信扫描右上方二维码,关注我的微信公众号“编程老司机”,获取最新推送文章.
最后此篇关于从源码级深入剖析Tomcat类加载原理的文章就讲到这里了,如果你想了解更多关于从源码级深入剖析Tomcat类加载原理的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我的 Django 应用程序在生产过程中变得非常缓慢。可能是由于某些复杂或未索引的查询。 是否有任何类似 django 的方法来分析我的应用程序? 最佳答案 试试 Django Debug Toolb
我正在使用GDownloadUrl将客户端数据发送到服务器。这里是我使用的sode GDownloadUrl( dwnld_url, function(data) {
我一直在尝试开始分析我的 CherryPy 网络服务器,但文档缺乏关于如何设置它的详细信息。我知道我应该能够使用 cherrypy.lib.profiler 作为中间件来安装我的初始服务器。现在,我有
是否有任何 HashMap 实现公开了用于分析 Map 性能的钩子(Hook)方法(平均链长度、最佳/最差/平均访问时间、#rehashes 等)。 在 ~O(1) 访问时间方面使用 HashMap
我想知道是否有可用的技术或工具可以告诉您执行特定方法需要多少时间。 类似于数学/计算机科学中的大 O 符号,可以让您了解算法的复杂性,我想知道代码分析是否有类似的东西。 最佳答案 Profiling是
前面,我们说Ruby没有函数,只有方法.而且实际上有不止一种方法.这一节我们介绍访问控制(accesscontrols). 想想当我们在"最高层"而不是在一个类的定义里定义一个
我有一个用 Visual Basic 编写的大型应用程序6,我需要分析它。有没有类似 ANTS Profiler 的东西但对于 COM应用程序? 最佳答案 我以前用 VBWatch .我对它有很好的体
我做了两个实现来解决 Shikaku 难题。一个使用顶部、左侧、宽度和高度 (TLWH) 作为每个矩形的参数,另一个使用顶部、左侧、底部、右侧 (TLBR)。 出于某种原因,使用 TLBR 的速度要快
如果重要的话,我正在使用 Very Sleepy CS 进行分析。 IPP(Intel's Integrated Performance Primitives)在我的项目中使用,基本上是单次调用: i
我想弄明白这个排列算法是如何工作的: def perm(n, i): if i == len(n) - 1: print n else: for j i
如果 C++ 项目的物理结构有利于编译速度,您将如何获得某种客观的衡量标准?有些人说你应该避免使用模板以获得更好的编译速度,但是如果模板生成大量非冗余目标代码,即使行/秒编译速度不是很好,那也不是很糟
摘自 Robert Sedgewick 和 Kevin Wayne 算法第 4 版 在递归部分基本情况代码是 if(end start) { mid = (start+end
有没有一种简单的方法可以计算一段标准 C 代码实际执行的乘法次数?我想到的代码基本上只是做加法和乘法,主要兴趣是乘法,但也可以计算其他操作的数量。 如果这是一个选项,我想我可以四处用“multiply
我正在编写一个 Netty 应用程序。该应用程序运行在 64 位八核 linux 机器上 Netty 应用程序是一个简单的路由器,它接受请求(传入管道),从请求中读取一些元数据并将数据转发到远程服务(
我希望能得到一些帮助来弄清楚这个异常消息到底对我说了什么。我能够使用调试器将问题缩小到代码中的特定行。但是,我认为更多信息可以更好地找出代码中的实际问题。 public static List
我有一个存储在 USB 拇指驱动器上的 mysql 数据库,该数据库已不可挽回地丢失了其文件分配表。因此,我无法访问整个 ibdata1 文件。不过,我可以找到使用十六进制编辑器使用的记录页面。 所有
我正在使用 jQuery 在单击时显示图像。通过将图像源存储到像这样的变量中,这可以很好地工作.. var theSrc = $(event.target).attr('src'); 然后我可以将这张
我是 R 的新手,但不是 C 的新手。我想看看是否可以为 friend 加速一个包。通常使用 C 我会编译一个设置了 -pg 标志的应用程序,然后将 gmon.out 文件传递给 gprof。 我
在分析我的代码以找出运行缓慢的地方时,我有 3 个功能显然会永远占用,这就是非常困的说法。 这些函数是: ZwDelayExecution 20.460813 20.460813 19.
我正在分析一个应用程序并注意到 52% (195MB) 的内存被 char[] 使用,20% 被 String 使用。这是一个有很多依赖项的大型项目,我刚刚看到它,所以我有几个相关的问题可以帮助我开始
我是一名优秀的程序员,十分优秀!