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

《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》笔记

1、概述

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

2、案例分析

2.1、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 组。

`CommonClassLoader`、 `CatalinaClassLoader`、 `SharedClassLoader` 和 `WebappClassLoader` 则是 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 目录。

2.2、OSGi:灵活的类加载器架构

“学习 JEE 规范,去看 JBoss 源码;学习类加载器,就去看 OSGi 源码”。

OSGi 在 Java 程序员中最著名的应用案例就是Eclipse IDE。

一个 Bundle 可以声明它所依赖的 Java Package( 通过 Import- Package 描述),也可以声明它允许导出发布的 Java Package( 通过 Export- Package 描述)。

一个模块里只有被 Export 过的 Package 才可能由外界访问。

基于 OSGi 的程序很可能(只是很可能,并不是一定会)可以实现模块级的热插拔功能,当程序升级更新或调试除错时,可以只停用、重新安装然后启用程序的其中一部分。

OSGi 的 Bundle 类加载器之间只有规则,没有固定的委派关系。

只有具体使用某个 Package 和 Class 的时候,才会根据 Package 导入导出定义来构造 Bundle 间的委派和依赖。

如果一个类存在于 Bundle 的类库中但是没有被 Export, 那么这个 Bundle 的类加载器能找到这个类,但不会提供给其他 Bundle 使用

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

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

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

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

2.4、Retrotranslator:跨越JDK版本

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

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

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

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

3.1、目标

3.2、思路

3.3、实现

构造函数中指定为加载 HotSwapClassLoader 类的类加载器作为父类加载器,这一步是实现提交的执行代码可以访问服务端引用类库的关键。

3.4、验证

4、本章小结

arthinking wechat
欢迎关注itzhai公众号