深入理解java虚拟机:类加载及执行子系统的案例

news2024/11/29 10:48:55

文章目录

  • 1. 概述
  • 2. Tomcat:正统的类加载器结构
  • 3. OSGi:灵活的类加载器架构
  • 4. 字节码生成技术与动态代理的实现
  • 5. Retrotranslator:跨越JDK版本

1. 概述

在Class文件格式与执行引擎这部分里,用户的程序能直接影响的内容并不太多,Class文件以何种格式存储,类型何时加载、如何连接,以及虚拟机如何执行字节码指令等都是由虚拟机直接控制的行为,用户程序无法对其进行改变。能通过程序进行操作的,主要是字节码生成与类加载器这两部分的功能,但仅仅在如何处理这两点上,就已经出现了许多值得欣赏和借鉴的思路,这些思路后来成为了许多常用功能和程序实现的基础。在本章中,我们将看一下前面所学的知识在实际开发之中是如何应用的。

2. Tomcat:正统的类加载器结构

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

  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生成类的热替换,当然也有“非主流”的,如运行在生产模式(Production Mode)下的WebLogic服务器默认就不会处理JSP文件的变化。

由于存在上述问题,在部署Web应用时,单独的一个ClassPath就无法满足需求了,所以各种Web服务器都不约而同地提供了好几个ClassPath路径供用户存放第三方类库,这些路径一般都以libclasses命名。被放置到不同路径中的类库,具备不同的访问范围和服务对象,通常,每一个目录都会有一个相应的自定义类加载器去加载放置在里面的Java类库。现在,我们以Tomcat服务器为例,看一看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自定义了多个类加载器,这些类加载器按照经典的双亲委派模型来实现,其关系如图所示。
在这里插入图片描述
灰色背景的三个类加载器是JDK默认提供的类加载器,这三个加载器在 深入理解java虚拟机:虚拟机类加载机制(2) 中已经详细介绍过了。而CommonClassLoaderCatalinaClassLoaderSharedClassLoaderWebappClassLoader则是Tomcat自己定义的类加载器,它们分别加载/common//server//shared/WebApp/WEB-INF/中Java类库的逻辑。其中WebApp类加载器和Jsp类加载器通常会存在多个实例,每一个Web应用程序对应一个WebApp类加载器,每一个JSP文件对应一个Jsp类加载器。

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

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

3. OSGi:灵活的类加载器架构

Java程序社区中流传着这么一个观点:“学习JEE规范,去看JBoss源码;学习类加载器,就去看OSGi源码 ”。尽管“JEE规范”和“类加载器的知识”并不是一个对等的概念,不过,既然这个观点能在程序员中流传开来,也从侧面说明了OSGi 对类加载器的运用确实有其独到之处。

OSGi(Open Service Gateway Initiative) 是OSGi联盟(OSGi Alliance)制订的一个基于Java语言的动态模块化规范,这个规范最初由Sun、IBM、爱立信等公司联合发起,目的是使服务提供商通过住宅网关为各种家用智能设备提供各种服务,后来这个规范在Java的其他技术领域也有相当不错的发展,现在已经成为Java世界中“事实上”的模块化标准,并且已经有了Equinox、Felix等成熟的实现。OSGi在Java程序员中最著名的应用案例就是Eclipse IDE,另外还有许多大型的软件平台和中间件服务器都基于或声明将会基于OSGi规范来实现,如IBM Jazz平台、GlassFlish服务器、Weblogic10.3所使用的mSA架构等。

OSGi中的每个模块(称为 Bundle )与普通的Java类库区别并不太大,两者一般都以 JAR 格式进行封装,并且内部存储的都是Java Package和Class。但是一个Bundle可以声明它所依赖的Java Package(通过Import-Package描述),也可以声明它允许导出发布的Java Package(通过Export–Package描述)。在OSGi里面,Bundle之间的依赖关系从传统的上层模块依赖底层模块转变为平级模块之间的依赖(至少外观上是如此),而且类库的可见性能得到了非常精确的控制,一个模块里只有被Export过的Package才可能被外界访问,其他的Package和Class将会被隐藏起来。除了更精确的模块划分和可见性控制外,引入OSGi的另外一个重要理由是,基于OSGi的程序很可能(只是很可能,并不是一定会)可以实现模块级的热插拔功能,当程序升级更新或调试除错时,可以只停用、重新安装然后启用程序的其中一部分,这对企业级程序开发来说是一个非常有诱惑力的特性。

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

“字节码生成”并不是什么高深的技术,我们在看到“字节码生成”这个标题时也不必先去想诸如JavassistCGLibASM之类的字节码类库,因为JDK里面的 javac命令就是字节码生成技术的“老祖宗”,并且javac也是一个由Java语言写成的程序,它的代码存放在OpenJDK的jdk7/langtools/src/share/classes/com/sun/tools/javac目录中。要深入了解字节码生成,阅读javac的源码是个很好的途径,不过javac对于我们这个例子来说太过庞大了。在Java里面除了javac和字节码类库外,使用到字节码生成的例子还有很多,如Web服务器中的JSP编译器,编译时织入的 AOP框架,还有很常用的 动态代理技术,甚至在使用反射的时候虚拟机都有可能会在运行时生成字节码来提高执行速度。我们选择其中相对简单的动态代理来看看字节码生成技术是如何影响程序运作的。

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 originObj){
            this.originalObj = originObj;
            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){
        // jdk 8.0以前
  		System.getProperties().put("sum.misc.ProxyGenerator.saveGeneratedFiles","true");
        // jdk 8.0以后
        System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles","true");
        IHello hello = (IHello) new DynamicProxy().bind(new Hello());
        hello.sayHello();
      }
}
/*
welcome
hello world
*/

在这里插入图片描述
代理类通过 JD-GUI 的反编译:查看$Proxy0.class

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

final class $Proxy0 extends Proxy implements Test.IHello {
  private static Method m1;
  
  private static Method m3;
  
  private static Method m2;
  
  private static Method m0;
  
  public $Proxy0(InvocationHandler paramInvocationHandler) {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject) {
    try {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final void sayHello() {
    try {
      this.h.invoke(this, m3, null);
      return;
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final String toString() {
    try {
      return (String)this.h.invoke(this, m2, null);
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final int hashCode() {
    try {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  static {
    try {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("Test$IHello").getMethod("sayHello", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    } catch (NoSuchMethodException noSuchMethodException) {
      throw new NoSuchMethodError(noSuchMethodException.getMessage());
    } catch (ClassNotFoundException classNotFoundException) {
      throw new NoClassDefFoundError(classNotFoundException.getMessage());
    } 
  }
}

具体的动态代理相关,参考 动态代理的介绍

5. Retrotranslator:跨越JDK版本

一般来说,以“做项目”为主的软件公司比较容易更新技术,在下一个项目中换一个技术框架、升级到最时髦的JDK版本、甚至把Java换成C#来开发都是有可能的。在Java的世界里,每一次JDK大版本的发布,就伴随着一场大规模的技术革新,而对Java程序编写习惯改变最大的,无疑是JDK1.5的发布。自动装箱、泛型、动态注解、枚举、变长参数、遍历循环(foreach循环)…事实上在没有这些语法特性的年代,Java程序也照样能写。由于客观原因,必须使用1.5以前版本的JDK呢?把JDK1.5中编写的代码放到JDK1.4或1.3的环境中去部署和使用。为了解决这个问题,一种名为 Java逆向移植 的工具(Java Backporting Tools)应运而生,Retrotranslator是这类工具中最出色的一个。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/49168.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Tableau数据分析数据可视化分析平台

Tableau数据分析&数据可视化分析平台​ 本文章内涉及的资源包以及素材均来自于互联网,仅供大家用来交流学习与研究使用,努力提升自己的一篇文章。各类安装包以及素材版权归属原版权方所有,版权争议与本人无关,用户本人下载后不…

Linux用户操作

用户操作 1、切换用户 root登录 切换到普通用户 howard 命令:su howard 再切换到root用户 一个是执行exit命令,二个是执行su root 查看环境变量 命令:env 切换到Howard用户查看 2、查看用户 查看所有用户 命令:cat …

深入理解Linux网络技术内幕(十)——帧的接收

文章目录前言与其他功能交互设备的开启和关闭队列通知内核帧已接收:NAPI和netif_rxNAPI简介NAPI所用之net_device字段net_rx_action和NAPI新旧驱动程序接口操作poll_list设备驱动程序与内核间的旧接口:netif_rx的第一部分netif_rx的初始任务管理队列以及…

2022最新鸽哒IM即时通讯系统源码 带安卓、苹果、PC端(全开源)+部署教程

提示:即时通讯,纯原生开发,各种功能应有尽有 内容目录一、详细介绍二、效果展示1.部分代码2.效果图展示三、学习资料下载一、详细介绍 提示:即时通讯,纯原生开发,各种功能应有尽有 鸽哒是一款类似于v的即时…

供应生物素PEG衍生物试剂Biotin-PEG-NHS,NHS-PEG-Biotin,生物素PEG活性酯

1、名称 英文:Biotin-PEG-NHS,NHS-PEG-Biotin 中文:生物素-聚乙二醇-活性酯 2、CAS编号:N/A 3、所属分类:Biotin PEG NHS ester PEG 4、分子量:可定制,活性酯-聚乙二醇2-生物素&#xff0c…

Java RMI 远程代码执行漏洞

0x01 漏洞描述 - Java RMI 远程代码执行漏洞 - Java RMI服务是远程方法调用,是J2SE的一部分,能够让程序员开发出基于JAVA的分布式应用。一个RMI对象是一个远程Java对象,可以从另一个Java虚拟机上(甚至跨过网络)调用它…

SpringBoot项目打印接口请求日志,CommonsRequestLoggingFilter实现方式

文章目录需求背景效果图实现思路其他方案对比优缺点分析具体实现需求背景 线上项目出现bug时,可以通过接口的请求参数来排查定位问题。和业务方battle时,能够回怼他是自己操作的问题。 效果图 实现思路 Spring提供了CommonsRequestLoggingFilter过滤器…

opengl,opengl es,egl,glfw,glew

OpenGL ES之GLFW窗口搭建 - Plato - 博客园概述 本章节主要总结如何使用GLFW来创建Opengl窗口。主要包括如下内容: OpenGl窗口创建介绍 GLFW Window版编译介绍 GLFW简单工程源码介绍 OpenGL窗口创建介绍 能用于Ohttps://www.cnblogs.com/feng-sc/p/5093262.htmlOp…

事务-P26,P27

事务:要么都成功,要么都失败。例子:支付宝交易。 acid原则。 11直接移植10的代码 spring-11-transaction: UserMapper(增加两个接口) package com.Li.mapper;import com.Li.pojo.User;import java.util.…

C# 消息 界面卡顿 界面进程 工作进程

一 消息 消息与消息循环,是所有的GUI开发里共同的概念: 消息Message,有的地方也叫事件; ① 鼠标消息; ② 键盘消息; ③ 绘制事件; ④ 窗口最大化、最小化; 1 消息循环 消息循环,M…

10道不得不会的缓存面试题【缓存】

博主介绍: 🚀自媒体 JavaPub 独立维护人,全网粉丝15w,csdn博客专家、java领域优质创作者,51ctoTOP10博主,知乎/掘金/华为云/阿里云/InfoQ等平台优质作者、专注于 Java、Go 技术领域和副业。🚀 最…

使用Conda

0. Anaconda Prompt 命令提示符 0.1 验证conda是否被安装 conda --version0.2 conda管理环境 可以用命令复制和删除环境 参考. 1. Conda管理包 1.1 常用包管理功能 查找包查看包安装包 查找分为精确查找和模糊查找,如下图所示 卸载包更新包 1.2 conda管理环…

【实习之velocity】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、图示二、应用场景1.Web应用程序:作为为应用程序的视图,显示数据2.源代码生成:Velocity可用于基于模板生成java源代码3.自…

[c++基础]-vector类

前言 作者:小蜗牛向前冲 名言:我可以接受失败,但我不能接受放弃 如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。 目录 一、 vect…

scanf导致程序运行时出现stack smashing

一 栈溢出stack smashing 程序在运行期间破坏了已在操作系统里定义好的栈边界&#xff0c;这种行为具有破坏性&#xff0c;操作系统使用stack smashing detect机制来检测栈溢出。 二 栈溢出简单例子 实例一&#xff1a; #include <stdio.h> #include <stdlib.h>…

基于智能优化算法实现的机械臂避障路径规划(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 针对目前空间机械臂避障路径规划算法计算量大难以达到在线实时规划的缺点,对空间机械臂的在线实时避障路径规划问题进行了研究和…

计算机四级网络-网络技术-第六章 网络管理与网络安全

6.1 网络管理技术 CMIP 采用委托监控机制。 CMIP协议是由IS0组织制定的一种管理协议。管理进程根据事件发生时对网络服务影响的大小来划分事件的严重等级&#xff0c;然后再产生相应的故障处理方案。CMIP的所有功能都要映射到应用层的相关协议上实现。操作和事件报告是通过远…

如何使用腾讯会议录屏?腾讯会议不允许录屏怎么办?这个方法教你解决

​近年来&#xff0c;线上活动越来越活跃&#xff0c;很多人都开始使用腾讯会议进行开会、网课教学等活动。很多人希望使用腾讯会议进行录屏。那么如何使用腾讯会议录屏&#xff1f;腾讯会议不允许录屏怎么办&#xff1f;这个方法教你解决&#xff01; 一、如何使用腾讯会议录屏…

计算机组成原理习题课第三章-5(唐朔飞)

计算机组成原理习题课第三章-5&#xff08;唐朔飞&#xff09; ✨欢迎关注&#x1f5b1;点赞&#x1f380;收藏⭐留言✒ &#x1f52e;本文由京与旧铺原创&#xff0c;csdn首发&#xff01; &#x1f618;系列专栏&#xff1a;java学习 &#x1f4bb;首发时间&#xff1a;&…

[ElasticSearch]-初识Elastic Stack

[ElasticSearch]-初识Elastic Stack 森格 | 2022年11月 本文是第一次接触Elasticsearch的一些个人总结&#xff0c;介绍了Elastic Stack&#xff0c;如有错误之处&#xff0c;敬请指正。 一、ELK 到 Elastic Stack 注&#xff1a;上图来源于Elasticsearch中文博客 ELK分别是Ela…