概述

在Class文件格式与执行引擎这部分中,用户的程序能影响的内容并不太多,能通过程序进行操作的,主要是字节码生成与类加载器这两部分的功能。

案例分析

Tomcat:正统的类加载架构

主流的Java Web服务器如Tomcat、Jetty、WebLogic、WebSphere都实现了自己的类加载器。一个功能健全的服务器,要解决如下几个问题:

  • 部署在同一个服务器上的两个Web应用程序所使用的Java类库可以实现互相隔离。这是最基本的需求,两个不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求一个类库在一个服务器中只有一份,服务器应当保证两个应用程序的类库可以独立使用;
  • 部署在同一个服务器上的两个Web应用程序所使用的Java类库可以互相共享。例如,用户可能有10个使用Spring组织的应用程序部署在同一台服务器上,如果把10份Spring分别存放在各个应用程序的隔离目录中,将会是很大的资源浪费——类库在使用时都要被加载到服务器内存,如果类库不能共享,虚拟机的方法区就会很容易出现过度膨胀的风险。
  • 服务器需要尽可能的保证自身的安全不受部署的Web应用程序影响,有许多Java Web服务器也是用Java语言实现的,因此,服务器本身也有类库依赖的问题,一般来说,基于安全考虑,服务器所使用的类库应该与应用程序的类库相互独立;
  • 支持JSP应用的Web服务器,大多数都需要支持HotSwap功能。JSP文件最终要编译成Java Class才能由虚拟机执行,但JSP文件由于其纯文本存储的特性,运行时修改的概率远远大于第三方类库或程序自身的Class文件。而且ASP、PHP和JSP这些网页应用也把修改后无需重启作为一个很大的优势来看待,因此,主流的Java Web服务器都会支持JSP生成类的热替换,也有的服务器不会处理JSP文件变化,如运行在生产模式下的WebLogic服务器。

基于上述问题,在部署web应用时,单独的一个ClassPath就无法满足需求了,所以各种服务器都提供了好几个ClassPath路径供用户存放第三方类库,这些路径一般以lib或classes命名。放置到不同路径中的类库,具备不同的访问范围和服务对象,通常,每一个目录都会有一个相应的自定义类加载器去加载放置在里面的java类库。

Tomcat5之前,类库存放在四组目录中:”/commom”、”/server”、”/shared”和Web应用自身目录”/WEB-INF”,现在,前三个目录都被合并到lib目录下,不过可以在conf/catalina.properties查看三者指定的加载器,其中common.loader指定了加载器路径为lib,其余两项为空,除非自己修改server.loader和shared.loader,否则默认使用CommonClassLoader代替。下面是四个目录的区别:

  • common:类库可被Tomcat和所有的Web应用程序共同使用;
  • server:类库可被Tomcat使用,对所有Web应用程序都不可见;
  • shared:类库可被所有Web应用程序共同使用,但对Tomcat自己不可见;
  • WEB-INF:类库仅仅可以被次Web应用程序使用,对Tomcat和其他Web应用程序都不可见。

Tomcat定义了多个类加载器,按双亲委派模型实现,如图所示:

CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebAppClassLoader是Tomcat自定义的加载器,分别对应common、server、shared和WEB-INF的类库。其中WebApp类加载器和Jsp类加载器通常会存在多个实例,每一个Web应用程序对应一个WebApp类加载器,每一个JSP文件对应一个Jsp类加载器。

从上图可以看出,CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用,而CatalinaClassLoader和SharedClassLoader自己能加载的类则与对方隔离。WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。Jsp的加载范围仅仅是这个JSP文件编译出来的那一个Class,他出现的目的是为了被丢弃:当服务器检测到JSP文件被修改时,会替换掉目前的JasperLoader实例,并通过再建立一个新的Jsp类加载器来实现热替换功能。

要在Tomcat部署多个Spring应用程序,可以把Spring放到lib目录下让这些程序共享,Spring可以通过线程上下文类加载器由父类加载器访问子类加载器,创建线程时会把类加载器设置为WebApp类加载器。

OSGi(Open Service Gateway Initiative):灵活的类加载器架构

OSGi是OSGi联盟制定的一个基于Java语言的动态模块化规范,目的是使服务提供商通过住宅网关为各种家用智能设备提供各种服务,现在已经成为了Java世界中事实上的模块化标准。著名的案例是Eclipse IDE,另外还有许多大型的软件平台和中间件服务器都基于或声明将会基于OSGi规范来实现。

OSGi中的每个模块(Bundle)与普通的Java类库区别不大,两者一般都以JAR格式进行封装,并且内部存储的是Java Package和Class,但是一个Bundle可以声明他所依赖的Java Package(Import-Package),也可以声明它允许导出发布的Java Package(Export-Package)。在OSGi里,Bundle之间的依赖关系从传统的上层模块依赖底层模块变为平级模块之间的依赖,而且类库可见性得到精确的控制。此外,基于OSGi的程序很可能可以实现模块级别的热插拔功能,当程序升级更新或调试除错时,可以只停用、重新安装然后启用程序的其中一部分。

OSGi之所以有上述特点,要归功于它灵活的类加载器架构,OSGi的Bundle类加载器之间只有规则,没有固定的委派关系。不涉及具体的package时,各个Bundle加载器都是平级关系,只有具体使用某个package和Class的时候,才会根据Package导入导出定义来构造Bundle间的委派和依赖。加载一个类可能发生的查找行为和委派关系规则如下:

  • 以java.*开头的类,委派给父类加载器加载;
  • 否则,委派列表名单内的类,委派给父类加载器加载;
  • 否则,Import列表中的类,委派给Export这个类的Bundle的类加载器加载;
  • 否则,查找当前Bundle的ClassPath,使用自己的类加载器加载;
  • 否则,查找是否在自己的Fragment Bundle中,如果是,则委派给Fragment Bundle的类加载器加载;
  • 否则,查找Dynamic Import列表的Bundle,委派给对应的Bundle的类加载器加载;
  • 否则,类查找失败。

由于loadClass是synchronized方法,在加载时容易造成死锁,可以用osgi.Classloader.singleThreadLoads参数强制按单线程方式加载,JDK1.7也为非树状继承关系下的类加载器架构进行了一次专门的升级,从底层避免死锁。

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

使用字节码生成技术的例子有很多,如javac、Web服务器的JSP编译器、编译时植入的AOP框架、动态代理技术等。

public class DynamicProxyTest {interface IHello{void sayHello();}static class Hello implements IHello{@Overridepublic 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);}@Overridepublic 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

在上面的代码中,newProxyInstance方法调用了generateProxyClass方法来完成生成字节码的动作,这个方法可以在运行时产生一个描述代理类的字节码数组。

Retrotranslator:跨越JDK版本

要把新版本的代码放到旧版本JDK环境中去部署使用,需要使用Java逆向移植的工具(Java Backporting Tools),如Retrotranslator。

Retrotranslator的作用是将JDK1.5编译出来的Class文件转换为可以在JDK1.4或1.3上部署的版本,它可以很好地支持自动装箱、泛型、动态注解、枚举、变长参数、遍历循环、静态导入这些语法特性,甚至支持集合改进、并发包以及对泛型、注解等的反射操作。

JDK每次新增的功能大致分为以下四类:

  • 在编译器层面做的改进。如自动装箱拆箱,实际上就是编译器在程序中使用到包装对象的地方自动插入了很多Integer.valueOf()之类的代码,变长参数在编译之后就变成了一个数组来完成传递,泛型的信息则在编译阶段就已经擦除掉了,相应的地方被编译器自动插入了类型转换代码;
  • 对Java API的代码增强。譬如1.2引入的Collections、1.5引入的concurrent并发包等;
  • 需要在字节码中进行支持的改动。如1.7加入的动态语言支持,为字节码增加一条invokedynamic指令;
  • 虚拟机内部的改进。如1.5实现的JSR-133规范重新定义的Java内存模型、CMS收集器之类的改动。

上述四类新功能中,Retrotranslator只能模拟前两类,第二类模拟更容易实现一些。以concurrent为例,Retrotranslator附带了一个名叫“backport-util-concurrent.jar”的类库来代替。

Retrotranslator使用ASM框架直接对字节码进行处理,来处理JDK在编译层面做的改进。由于组成Class文件的指令不变,所以能用字节码表示的语义范围不变,但是元数据信息和一些语法支持的内容还是要做相应的修改。例如枚举类,Retrotranslator所做的处理就是把枚举类的父类从“java.lang.Enum”替换为它运行时类库中包含的“net.sf.retrotranslator.runtime.java.lang.Enum_”,然后在类和字段的访问标志中抹去ACC_ENUM标志位。同时,value()和valueOf()方法都要重写,常量池需要引入大量的新的来自父类的符号引用。

类加载及执行子系统的案例分析相关推荐

  1. 深入理解Java虚拟机(第二版) 第九章:类加载及执行子系统的案例与实战

    第九章 类加载及执行子系统的案例与实战 9.1 概述 9.2 Tomcat: 正统的类加载器架构 9.3 OSGi:灵活的类加载器架构 9.4 字节码生成技术与动态代理的实现 9.5 Retrotra ...

  2. JVM——类加载及执行子系统的案例与实战(Tomcat)

    摘要 本文将深入的学习与分析JVM虚拟机的原理和相关的调优的相关实例. 类加载及执行子系统的案例与实战 Tomcat: 正统的类加载器架构 主流的Java Web服务器, 如Tomcat. Jetty ...

  3. java逻辑第九章_深入理解jvm-(第九章)类加载及执行子系统的案例与实战

    转载自:http://blog.csdn.net/coslay/article/details/49564789 概述 在Class文件格式与执行引擎这部分中,用户的程序能直接影响的内容并不太多, C ...

  4. 第三部分 虚拟机执行子系统

    第6章 类文件结构 第7章 虚拟机类加载机制 第8章 虚拟机字节码执行引擎 第9章 类加载及执行子系统的案例 与实战

  5. 深入理解Java虚拟机-虚拟机执行子系统

    本博客主要参考周志明老师的<深入理解Java虚拟机>第二版 读书是一种跟大神的交流.阅读<深入理解Java虚拟机>受益匪浅,对Java虚拟机有初步的认识.这里写博客主要出于以下 ...

  6. JVM学习笔记【2】 类加载执行子系统

    1.JVM的简图 简要版 中文的详细版(宋文康老师教学图) 注意:方法区只有HotSpot虚拟机有,J9,JRockit都没有 今天主要的是学习类加载执行子系统 2.类加载执行子系统的作用 类加载器子 ...

  7. 【Java书笔记】:《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》第2部分-自动内存管理,第3部分-虚拟机执行子系统,第5部分-高效并发

    作者:周志明 整理者GitHub:https://github.com/starjuly/UnderstandingTheJVM 第2部分-自动内存管理 第2章 Java内存区域与内存溢出异常 2.2 ...

  8. linux 定位 踩内存_运维必备的问题定位工具及案例分析

    [摘要]本文主要介绍各种问题定位的工具,并结合案例分析问题. 1. 背景 有时候会遇到一些疑难杂症,并且监控插件并不能一眼立马发现问题的根源.这时候就需要登录服务器进一步深入分析问题的根源.那么分析问 ...

  9. 【收藏】运维必备的问题定位工具及案例分析

    [欢迎关注微信公众号:厦门微思网络] 微思网络(官网):https://www.xmws.cn/ [摘要]本文主要介绍各种问题定位的工具,并结合案例分析问题. [作者]李航,多年的底层开发经验,在高性 ...

最新文章

  1. MaxScale:实现MySQL读写分离与负载均衡的中间件利器
  2. 统计学习笔记(1)——统计学习方法概论
  3. ubuntu 13.04 mysql_Ubuntu13.04 下MySQL5.6安装过程
  4. 汇编:实现日历星期数查询工具
  5. php文件名解析漏洞 nginx,nginx服务器解析漏洞(一)
  6. 把人工智能、机器学习、深度学习串一串,串一个同心圆
  7. How to adjust IFrame height on it's content (转载)
  8. ntko 发生错误:保存html到临时文件目录发生错误,【ntko文件存取错误】_如何处理OA系统在线阅读或编辑文档时提示“文件存取错误”的问题...
  9. 【源码】手把手教你用Python实现Vivado和ModelSim仿真自动化
  10. 换IP软件用户如何选择?
  11. Android 手写签名 (图片合成)
  12. 一加nfc门禁卡录入_一加7t怎么开启NFC 模拟门禁卡方法介绍
  13. vim命令模式和底线_VIM 底线命令模式
  14. c#延时函数,不止Sleep函数
  15. 服务器win10系统开机慢,三种方法教你解决Win10系统开机慢,爱纯净官网
  16. 水经注CAD智能影像加载插件使用教程
  17. 苹果处理器排行_最新 iOS 性能排行榜,你的设备落伍了吗?
  18. 微信WeChatHelper3.1.0.72逆向-微信WeChatHelper3.1.0.72接口(WeChatHelper3.1.0.72.dll)-VC++调用实例方法(win32)
  19. Web前端大作业—个人网页(html+css+javascript)
  20. 逻辑运算符以及逻辑表达式

热门文章

  1. c++ 总结之 cout输出
  2. 就算砸下1300亿造车,苹果颠覆行业也有点难
  3. java 进制转换工具_进制转换工具(JAVA)
  4. 如今只见当年月,何曾再见当年人?
  5. BIM模型文件下载——多层住宅楼建筑群施工场地模拟模型
  6. 大四实习已offer公司
  7. Nginx :user nobody
  8. android path 画星星
  9. matlab heart scale,matlab下libsvm测试heart_scale的问题
  10. 鸿蒙手机电脑无缝对接,华为鸿蒙超级终端功能曝光,自动连接附近设备实现无缝协同工作...