类加载及执行子系统的案例与实战

概述

能通过程序进行操作的,主要是字节码生成与类加载器这两部分的功能。

案例分析

Tomcat:正统的类加载器架构

主流的 Java Web 服务器,如 Tomcat、 Jetty、 WebLogic、 WebSphere 或其他笔者没有列举的服务器,都实现了自己定义的类加载器(一般都不止一个)。因为一个功能健全的 Web 服务器,要解决如下几个问题:

  • 部署在同一个服务器上的两个 Web 应用程序所使用的 Java 类库可以实现相互隔离。
  • 部署在同一个服务器上的两个 Web 应用程序所使用的 Java 类库可以互相共享。
  • 服务器需要尽可能地保证自身的安全不受部署的 Web 应用程序影响。
  • 支持 JSP 应用的 Web 服务器,大多数都需要支持 HotSwap 功能。

在部署 Web 应用时,单独的一个 ClassPath 就无法满足需求了,所以各种 Web 服务器都“不约而同”地提供了好几个 ClassPath 路径供用户存放第三方类库。

在 Tomcat 目录结构中,有 3 组目录("/common/*"、"/server/*" 和"/shared/*") 可以存放 Java 类库,另外还可以加上 Web 应用程序自身的目录"/WEB-INF/*",一共 4 组。

  • /common/*:放在这个下面的类库,可以被Tomcat和多有的Web应用程序共同使用。
  • /server/*:类库可以被Tomcat使用,对所有的Web应用程序都不可见。
  • /shared/*:类库可被所有web应用程序共同使用,但对Tomcat自己不可见
  • /WEB-INF/*:类库仅仅可以被此Web应用程序使用,对Tomcat和其他web程序都不可见。

CommonClassLoaderCatalinaClassLoaderSharedClassLoaderWebappClassLoader 则是 Tomcat 自己定义的类加载器,

tomcat类加载器结构图

Tomcat热部署原理:

JasperLoader的加载范围仅仅是这个 JSP 文件所编译出来的那一个 Class, 它出现的目的就是为了被丢弃:当服务器检测到 JSP 文件被修改时,会替换掉目前的 JasperLoader 的实例,并通过再建立一个新的 Jsp 类加载器来实现 JSP 文件的 HotSwap 功能。

对于 Tomcat 的 6.x 版本,只有指定了 tomcat/conf/catalina.properties配置文件的 server.loader 和 share.loader 项后才会真正建立 CatalinaClassLoader 和 SharedClassLoader 的实例,否则会用到这两个类加载器的地方都会用 CommonClassLoader 的实例代替,而默认的配置文件中没有设置这两个 loader 项,所以 Tomcat 6.x 顺理成章地把/common、/server 和/shared 三个目录默认合并到一起变成一个/lib 目录。这个目录里的类库相当于以前/common目录中类库的作用。这是Tomcat设计团队为了简化部署所做的一项改进,如果默认设置不能满足需要,用户可以通过修改配置文件指定server.loader和share.loader的方式重新启动5.X的加载器架构。

OSGi:灵活的类加载器架构

Java社区有这样一句话:“学习JEE规范,去看JBoss源码;学习类加载器,就去看OSGi源码”。

OSGi在Java程序员中最著名的应用案例就是Eclipse IDE。另外还有许多大型的软件平台和中间件服务器都基于或声明将会基于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间的委托和依赖。

如果一个类存在于 Bundle 的类库中但是没有被 Export, 那么这个 Bundle 的类加载器能找到这个类,但不会提供给其他 Bundle 使用,而且OSGi平台也不会把其他Bundle的类加载器请求分配给这个Bundle来处理。

并非所有的应用都适合采用 OSGi 作为基础架构, OSGi 在提供强大功能的同时,也引入了额外的复杂度,带来了线程死锁和内存泄漏的风险。

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

javac 也是一个由 Java 语言写成的程序,它的代码存放在 OpenJDK 的 langtools/src/share/classes/com/sun/tools/javac 目录中。 要深入了解字节码生成,阅读 javac 的源码是个很好的途径。

动态代理实现了可以在原始类和接口还未知的时候,就确定代理类的代理行为,当代理类与原始类脱离直接联系后,就可以很灵活地重用于不同的应用场景之中。调用了 sun.misc.ProxyGenerator.generateProxyClass() 方法来完成生成字节码的动作,这个方法可以在运行时产生一个描述代理类的字节码 byte[] 数组。

这里只是列出简单的例子,有时间可以去原书看看!P282.

Retrotranslator:跨越JDK版本

一种名为“ Java 逆向移植”的工具( Java Backporting Tools)应运而生, Retrotranslator[1] 是这类工具中较出色的一个。

编译器在程序中使用到包装对象的地方自动插入了很多 Integer.valueOf()、 Float.valueOf() 之类的代码;变长参数在编译之后就自动转化成了一个数组来完成参数传递;泛型的信息则在编译阶段就已经擦除掉了(但是在元数据中还保留着),相应的地方被编译器自动插入了类型转换代码[ 2]。

从字节码的角度来看,枚举仅仅是一个继承于 java. lang. Enum、 自动生成了 values() 和 valueOf() 方法的普通 Java 类而已。

具体看原书!

实战:自己动手实现远程执行功能

看完原书并实践之后,再来补

参考

JVM笔记 – 虚拟机执行子系统(类加载及执行子系统的案例与实战)

results matching ""

    No results matching ""