第九章 类加载及执行子系统的案例与实战_子系统案例-程序员宅基地

技术标签: JVM  

Tomcat:正统的类加载器架构

一个功能健全的类加载器,都要解决以下几个问题:
(1)部署在同一服务器上的两个web应用程序所使用的java类库可以实现相互隔离。这是最基本的需求,两个不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求一个类库在一个服务器中只有一份,服务器应当可以保证两个应用程序的类库可以相互独立使用。
(2)部署在同一个服务器上的两个web应用程序所使用的java类库可以相互共享,这个需求也很常见,例如用户可能有10个使用Spring组织的应用程序部署在同一台服务器上,如果把10分Spring分别放在各个应用程序的隔离目录中,将会是很大的资源浪费-----主要到不是浪费磁盘空间的问题,而是指类库在使用时都要被加载到服务器内存,如果类库不能共享,虚拟机的方法区很容易就会出现过度膨胀的危险。
(3)服务器需要尽可能的保证自身的安全不受部署的web应用程序影响,目前,很多主流的java web服务器自身也是使用java语言来实现的,因此服务器本身也有类库依赖问题,一般来说,基于安全考虑,服务器所使用的类库应该与应用程序的类库互相独立。
(4)支持JSP应用的web服务器,十有八九都需要支持HotSwap功能,我们知道JSP文件最终要被编译成java Class文件才能被虚拟机执行,但JSp文件由于其纯文本存储的特性,被运行时修改的概率远远大于第三方类库或程序自己的Class文件,而且ASP、PHP、JSP这些网页应用也把修改后无须重启作为一个很大的“优势”来看待,因此“主流”web服务器都会支持JSP生成类的热替换,当然也有“非主流”,如运行在生产模式下的webLogic服务器默认就不会处理JSP文件的变化。

  由于上述存在的问题,在部署web应用时,单独的一个ClassPath就无法满足需求了,所以各种web服务器都不约而同提供了好几个ClassPath路径用户存放第三方类库,这些路径一般都以“lib”或“classess”命名,被放置到不同路径中的类库,具备不同的访问范围和服务对象,通常,每一个目录都会有一个相应的自定义类加载器去加载放置在里面的jav类库,那么Tomcat是如何规划用户的类库结构和类加载器的?

在Tomcat目录结构中,有三组目录(“/common/”、“/server/”、和“/shared/”)可以存放在java类库中,另外还可以加上web应用程序自身的目录“/WEB-INF/”,一共四组,把java类库放置在这些目录中的含义分别是:
(1)放置在/common目录中;类库可被Tomcat和所有的web应用程序共同使用。
(2)放置在/server目录中:类库可被Tomcat使用,对所有的Web应用程序都不可见。
(3)放置在/shared目录中:类库可被所有web应用程序共同使用,但对Tomcat自己不可见。
(4)放置在/WebApp/WEB-INF目录中:类库仅仅可以被此web应用程序使用,对Tomcat和其他web应用程序都不可见。

为了支持这套目录结构,并对目录里面的类库进行加载和隔离,Tomcat自定义了多个类加载器,这些类加载器按照经典的双亲委派模型来实现。下图是tomcat服务器的类加载结构:

  灰色背景的三个类加载器时JDK默认提供的类加载器。而CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader则是Tomcat自己定义的类加载器,它们分别加载/common/、/server/、/shared/*和/WebApp/WEB-INF/*中java类库的逻辑,其中webApp类加载器和JSP类加载器通常会存在多个实例,每一个Web应用程序对应一个WebApp类加载器,每一个JSP文件对应一个JSP类加载器。

  从上图的委派关系可以看出,CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用,而CatalinaClassLoader和SharedClassLoader自己能加载的类则与对象相互隔离。WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离,而JasperLoader的加载范围仅仅是这个Jsp文件所编译出来的那一个Class,它出现的目的就是为了被丢弃:当服务器监测到Jsp文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现Jsp文件的HotSwap功能。
  对于Tomcat的6.x版本,只有指定了tomcat/conf/catalina.properties配置文件的server.loader和share.loader项后才会真正建立CatalinaClassLoader和SharedClassLoader的实例,否则会用到这两个类加载器的地方都会用CommonClassLoader的实例来替换,而默认的配置文件中没有设置这两个loader项,所以Tomcat6.x顺理成章的把/common、/server、/shared三个目录默认合并到一起变成一个/lib目录,这个目录里的类库相当于以前/commom目录张类库的作用。这是tomcat设计团队为了简化大多数的部署场景所做的一项改进,如果默认设置不能满足需要,用户可以通过修改配置文件制定server.loader和share.loader的方式重新启用Tomcat5.x的加载器架构。

OSGI:灵活的类加载器架构

  OSGI中的每个模块(称为Bundle)与普通的java类库区别并不太大,两者一般都以JAR格式进行封装,并且内部存储的都是Java Package和Class。但是一个Bundle可以声明它所依赖的Java Package(通过Import-Package描述),也可以声明它允许导出发布的Java Package(通过Export-Package描述)。在OSGI中,Bundle之间的依赖关系从传统的上层模块依赖底层模块变成平级模块之间的依赖(至少外观上是如此),而且类库的可见性能得到了非常精确的控制,一个模块里只有被Export过的Package才可能被外界访问,其他的Package和Class将会被隐藏起来,除了更精确的模块划分和可见性控制外,引入OSGI另外一个重要理由是,基于OSGI的程序很可能可以实现模块级的热插拔功能,当程序升级更新或调试除错时,可以只停用,重新安装然后启用程序的其中一部分,这对企业级程序开发来说是一个非常有诱惑力的特性。
  OSGI之所以能有上述诱人的特点,要归功于它灵活的类加载器结构,OSGI的Bundle类加载器之间只有规则,没有固定的委派关系,例如,某个Bundle声明了一个它依赖的Package,如果有其它Bundle声明发布了这个Package后,那么对这个Package的所有类加载动作都会委派给发布它的Bundle类加载器去完成,不涉及某个具体的Package时,各个Bundle加载器都是平级的关系,只有具体使用到某个Package和Class的时候,才会根据Package导入导出定义来构造Bundle间的委派和依赖。在OSGI里面,加载器之间的关系不再是双亲委派模型的树形结构,而是进一步发展成了一种运行时才能确定的网状结构。这种网站结构的类加载器在带来更优秀的灵活性的同时,也可能产生许多新的隐患。

字节码生成技术与动态代理的实现

  如果使用过Spring,那大多数情况都会用过动态代理,因为如果Bean是面向接口编程,那么在Spring内部都是通过动态代理的方式来对Bean进行增强的。动态代理中所谓的“动态”,是针对使用Java代码实际编写了代理类的“静态”代理而言的,他的优势不在于省去了编写代码类那一点工作量,而是实现了可以在原始类和接口还未知的时候,就确定代理类的代理行为,当代理类与原始类脱离直接联系后,就可以很灵活的重用于不同的应用场景之中。
  下面代码演示了一个最简单的动态代理的用法,原始的逻辑是打印一句“hello world”,代理类的逻辑是在原始类方法执行前打印一句“welcome”。我们先看一下代码,然后再分析JDK是如何做到的。

public class DynamicProxyTest {

	interface IHello {
		void sayHello();
	}
	static class Hello implements IHello {
		@Override
		public void sayHello() {
			System.out.println("hello world");
		}
	}
	static class DynamicProxy implements InvocationHandler {
		Object originalObj;
		Object bind(Object originalObj) {
			this.originalObj = originalObj;
			return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this);
		}
		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			System.out.println("welcome");
			return method.invoke(originalObj, args);
		}
	}
	public static void main(String[] args) {
		IHello hello = (IHello) new DynamicProxy().bind(new Hello());
		hello.sayHello();
	}
}

运行结果

welcome
hello world

  上述代码里,唯一的“黑匣子”就是Proxy.newProxyInstance()方法,除此之外再没有任何特殊之处。这个方法返回一个实现了IHello接口,并且代理了new Hello()实例行为的对象。跟踪这个方法的源码,可以看到程序进行了验证、优化、缓存、同步、生成字节码、显示类加载等操作,前面的步骤并不是我们关注的重点,而最后他调用了sun.misc.ProxyGenerator.generateProxyClass()方法来完成生成字节码的动作,这个方法可以在运行时产生一个描述代理类的字节码byte[]数组。

Retrotranslator:跨越JDK版本

  Retrotranslator的作用是将JDK 1.5编译出来的Class文件转变为在JDK 1.4huo1.3上部署的版本,他可以很好的支持自动装箱、泛型、动态注解、枚举、变长参数、遍历循环、静态导入这些语法特性,甚至还可以支持JDK 1.5中新增的集合改进、并发包以及对泛型、注解等的反射操作。
  以枚举为例,在JDK 1.5中增加了enum关键字,但是Class文件常量池的CONSTANT_Class_info类型常量并没有发生任何语义变化,仍然是代表一个类或接口的符号引用,没有加入枚举,也没有增加“CONSTANT_Enum_info”之类的“枚举符号引用”常量。所以使用enum关键字定义常量,虽然从Java语法上看起来与使用class关键字定义类、使用interface关键字定义接口是同一层次的,但实际上这是由Javac编译器做出来的假象,从字节码的角度来看,枚举仅仅是一个继承于java.lang.Enum、自动生成了values()和valueOf()方法的普通Java类而已。
  Retrotranslator对枚举所做的主要处理就是把枚举类的父类从“java.lang.Enum”替换为他运行时类库中包含的“net.sf.retrotranslator.runtime.java.lang.Enum_”,然后再在类和字段的访问标志中抹去ACC_ENUM标志位。当然,这只是处理的总体思路,具体的实现要比上面说的复杂得多。可以想想既然两个父类实现都不一样,values()和valueOf()的方法自然需要重写,常量池需要引入大量新的来自父类的符号引用,这些都是实现细节。下图是一个使用JDK 1.5编译的枚举类与被Retrotranslator转换处理后的字节码的对比图。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_33327639/article/details/100041691

智能推荐

边缘计算【智能+安全检测】系列教程--使用OpenCV+GStreamer实现真正的硬解码,完全消除马赛克-程序员宅基地

文章浏览阅读574次,点赞15次,收藏21次。通过现有博客的GStreamer的解码方式解码,大多情况应该存在上图马赛克的问题,请勿紧张,其实上述url并没有调用硬件解码,后面有调用硬件解码的ULR,我们来在接下来的介绍中释获。通过RTSP协议连接海康威视摄像头将图片送入AI推理,发现OpenCV的VideoCapture使用的是软解码大量消耗了cpu的资源,并无法做到实时数据处理存在5秒钟的延时。只能把解码这部分送到硬件解码器去处理分摊掉cpu的压力,

【软考高项】项目范围管理中的需求跟踪矩阵说明_需求跟踪矩阵是哪个阶段产生的-程序员宅基地

文章浏览阅读3.1k次。需求跟踪矩阵是把产品需求从其来源连接到能满足需求的可交付成果的一种表格。使用需求跟踪矩阵,把每个需求与业务目标或项目目标联系起来,有助于确保每个需求都具有业务价值。需求跟踪矩阵提供了在整个生命周期内跟踪需求的一种方法,有助于确保需求文件中被批准的每项需求在项目结束的时候都能实现并交付。跟踪需求的内容包括:业务需要、机会、目的和目标;项目目标;项目范围和WBS可交付成果;产品设计;产品研发;测试策略和测试场景;高层级需求到详细需求等。_需求跟踪矩阵是哪个阶段产生的

Tensorflow fisrt example ------------- 实现手写数字识别 无隐含层最浅的神经网络_tensorflow 径向基神经网络-程序员宅基地

文章浏览阅读155次。# -*- coding: utf-8 -*-"""Created on Tue Mar 13 14:59:24 2018@author: 102121Tensorflow fisrt example ------------- 实现手写数字识别 无隐含层最浅的神经网络"""import tensorflow as tffrom tensorflow.examples.tutori..._tensorflow 径向基神经网络

linux 安装Python3及部署工程后台运行和查看日志_部署在云服务器上的python项目如何查看运行日志-程序员宅基地

文章浏览阅读821次。tail -f filename 会把 filename 文件里的最尾部的内容显示在屏幕上,并且不断刷新,只要 filename 更新就可以看到最新的文件内容。yum install libffi-devel -y ----需要换成默认的python,否则会yum报错。-s, --sleep-interval=S 与-f合用,表示在每次反复的间隔休眠S秒。-q, --quiet, --silent 从不输出给出文件名的首部。--pid=PID 与-f合用,表示在进程ID,PID死掉之后结束。_部署在云服务器上的python项目如何查看运行日志

Sonar扫描配置_sonarlink-程序员宅基地

文章浏览阅读2.1k次。最近两周一直在开发公司给的一个任务。开发完毕后要配置sonar扫描花了半天时间研究了下。因为bug目前已经修完了。可以水一篇博客工具IDEA,SonarQube账号(因为我是用公司的账号。所以我是不用自己搭建sonar服务器的)步骤下载sonarLink插件:Ctrl+alt+s打开设置 ,plugin 搜索SonarLink(安装即可)安装完毕后你的idea底部应该是有SonarLink的标签的。点击那个扳手进行配置。配置connection和project(如果是第一次。这个是找不到._sonarlink

DSP简介-程序员宅基地

文章浏览阅读2.2k次。DSP,Digital Signal Processor,也就是数字信号处理器。这是一种具有特殊结构的微处理器,是以数字信号来处理大量信息的微处理器。将模拟信号转换成数字信号,用于专用处理器的高速实时处理。DSP的工作原理:接收外部输入的模拟信号,然后将其转换为数字信号(为0或1),再对数字信号进行运算处理,并在其他系统芯片中把数字数据解译回模拟数据或实际环境格式。_dsp

随便推点

pyqt5 实现两个窗口间的参数传递_pyqt5 给已打开的客户端传递信息-程序员宅基地

文章浏览阅读8.1k次,点赞2次,收藏27次。前言:今天打算实现自定义子窗口显示信息,之后将参数传递回主窗口。因为只会用QMainWindow创建窗口,又想实现对话框式的效果,网上查了查,发现都是采用QMainWindow和QWidget,而且因为这两个类没有exec()函数,还无法实现阻塞,实现参数回传给主窗口的方法是采用槽。感觉此类方法还是比较麻烦,需要写相关的函数,所以还是想实现类似QDialog式的效果,点击确认关闭后取回需要传递的参数。经过上午的折腾初步到达想要的效果。大体思路是:将QDialog作为子窗口的父级,利用其exec()函数造_pyqt5 给已打开的客户端传递信息

麒麟开源堡垒机安装部署测试及优缺点总结_麒麟堡垒机免费版限制-程序员宅基地

文章浏览阅读9.5k次。近期出于管理和检查需要,单位领导要求上堡垒机系统,测试了几个商业堡垒机,因为价格超过预算等原因都未购买,又测试了三个开源的堡垒机,感觉麒麟开源堡垒机功能最全,基本上和商业堡垒机一样,唯一的问题就是图形部分不开源,但因为我们的服务器基本上全是LINUX环境,telnet、ssh、ftp、sftp已经足够了因此将这套堡垒机已经用于生产环境。现在市场商业堡垒机价格太高,基本上都要到10万左右,我结合_麒麟堡垒机免费版限制

SPR使用说明_录制spr是什么意思-程序员宅基地

文章浏览阅读7.9k次,点赞2次,收藏9次。什么是SPR?SPR是一个客户端一段时间业务操作的系统运行情况的总结报告。这个报告涵盖了客户端JVM信息、物理内存、是否启用前台缓存、业务操作性能概要、远程调用、sql信息、客户端信息以及是否报错等内容。这个报告有利于诊断业务操作的性能以及报错问题。补丁说明NC65、NC633标准产品已自带SPR功能,其它版本支持情况说明:NC5X版本:不支持;NC633之前的NC6X版..._录制spr是什么意思

pointpillar 数据流_anchorheadsingle无法运行-程序员宅基地

文章浏览阅读1.1k次,点赞5次,收藏13次。网络部分包含4部分:(1)PillarVFE pcdet/models/backbones_3d/vfe/pillar_vfe.py # 3D卷积(2)PointPillarScatter pcdet/models/backbones_2d/map_to_bev/pointpillar_scatter.py # 2D卷积(3)BaseBEVBackbone pcdet/models/backbones_2d/base_bev_backbone.._anchorheadsingle无法运行

windows下采用 nginx配置websocket支持wss流程_windows服务器中使用nginx配置wss端口-程序员宅基地

文章浏览阅读1.3k次,点赞25次,收藏9次。OU 字段: Organization Unit,下属部门名称,也常用于显示证书相关信息,如证书类型、证书产品名称、身份验证类型、验证内容等。Win32 OpenSSL v1.1.1w Light,安装Win32 OpenSSL v1.1.1w最常用的软件包。:如果选择第一项,拷贝到系统目录,当其他软件使用的OpenSSL版本我们安装这个版本不同时,可能导致该软件无法使用。C:Country ,单位所在国家,为两位数的国家缩写,如: CN 是中国。直接在cmd中,输入命令,查看OpenSSL版本。_windows服务器中使用nginx配置wss端口

python使用requests模块下载文件_python 下载链接 ==200-程序员宅基地

文章浏览阅读1.7k次。需要requests模块pip install requests -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com要下载的凡在lst.txt里: 001_怀了你的孩子 002_开Q7的女人 003_我只爱你一个人 004_撞了邪的周末 005_暂且等等 006_他们要结婚了 007_孩子本来就是你的 008_给你的惩罚 009_以德报怨 _python 下载链接 ==200