当前位置: 首页 > news >正文

「JVM 原理使用」 实际开发中的应用

Class 文件格式、执行引擎主要以 Class 文件描述了存储格式、类何时加载、如何连接、VM 如何执行字节码指令,这些动作基本都是 JVM 直接控制,用户代码无法干预和改变;

用户可以干预的只有字节码生成、类加载器两部分,而这两部分的应用是许多常用功能和程序实现的基础;

文章目录

      • 1. Tomcat: 正统的类加载器架构
      • 2. OSGi: 灵活的类加载器架构
      • 3. 字节码生成技术与动态代理的实现
      • 4. Backport 工具: Java 逆向移植

1. Tomcat: 正统的类加载器架构

一个功能完备的 Web 服务器(Tomcat、Jetty、WebLogic、WebSphere),都要解决如下问题:

  • 部署在同一服务器的两个 Web 应用程序所使用的 Java 类库实现相互隔离,相互独立使用;
  • 部署在同一服务器的两个 Web 应用程序所使用的 Java 类库实现相互共享,减少资源(主要是内存,JVM 方法区)浪费;
  • 服务器要尽可能保障自身安全不受部署的 Web 应用程序影响,服务器使用的类库应与应用程序的类库相互独立;
  • 支持 JSP 的 Web 服务器大多支持 HotSwap 功能,而 JSP 纯文本存储的特性导致其被篡改的几率远大于 Java 类库和自身 Class 文件,因此也需要提供 Production Mode(生成模式) 下不处理 JSP 文件的功能(如 WebLogic);

单独的一个 ClassPath 无法满足 Web 应用的部署需求,因此 Web Server 各自提供了不同含义的第三方类库 ClassPath(lib、classes,各自对应不同的访问范围和服务对象),每个路径需要相应的自定义类加载器去加载里面的 Java 类库;

Tomcat 目录

  • /common/*,Common 类加载器负责,类库可以被 Tomcat 和所有 Web 应用程序共同使用;
  • /server/*,Catalina 类加载器(也称 Server 类加载器)负责,类库可被 Tomcat 使用,对所有 Web 应用程序不可见;
  • /shared/*,Shared 类加载器负责,类库可以被所有 Web 应用程序共同使用,对 Tomcat 不可见;
  • /lib/*,Tomcat 6 之后,默认不开启 server.loader 和 share.loader 两个类加载器,而是用 Common 类加载器替代,因此 /common/*/server/*/shared/* 三个路径默认合并到了一个 /lib/* 路径,相当于 /common/* 的作用,
  • /WEB-INF/*,Webapp 类加载器负责,每个 Web 应用程序对应一个 Webapp 类加载器,类库仅可被当前 Web 应用程序使用,对 Tomcat 和其他 Web 应用程序不可见;

请添加图片描述

JSP 类加载器负责 JSP 文件的加载,每个 JSP 文件对应一个 JasperLoader 类加载器;而 JSP 加载器存在的目的就是被丢弃,一旦 JSP 文件被修改,就需要新建一个 JSP 类加载器替换旧的,以实现 JSP 文件的 HotSwap 功能;

Common 类加载器能够加载的类可以被 Catalina 类加载器和 Shared 类加载器使用;Catalina 类加载器和 Shared 类加载器加载的类相互隔离;Webapp 类加载器可以使用 Shared 类加载器加载的类,但各 Webapp 类加载器加载实例之间相互隔离;

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

OSGi 的研究有助于学习类加载器的知识;

OSGiOpen Service Gateway Initiative,OSGi 联盟制定的一个基于 Java 语言的动态的模块系统规范(JDK 9 引入的 JPMS 是静态的模块系统);因与 JPMS 模块化特性重叠,OSGi 现在着重向动态模块化系统的方向发展,用于实现模块级热插拔等;许多大型软件平台和中间件服务器都是基于或声明将要基于 OSGi 规范来实现的(如 IBM Jazz、GlassFish、JBoss OSGi 等);

Bundle,OSGi 的模块,类似于普通 Java 类库,且都是以 JAR 格式封装,内部都是 Java 的 Package 和 Class;但 Bundle 可以额外声明它所依赖的 Package(Import Package)和它允许导出发布的 Package(Export Package,未 export 的 package 和 Class 对外将是不可见的),用于精准控制可见性和依赖关系;

OSGi 的 Bundle 类加载器之间只有规则,没有固定的委派关系;所有 Package 的类加载动作都会委派给发布它的 Bundle 类加载器来完成;不涉及某个具体 Package 时,所有 Bundle 加载器都是平级关系,只有具体到某个 Package 或 Class 时,才根据 Package 导入导出定义来构造 Bundle 间的委派和依赖;

假设存在 Bundle A、Bundle B、Bundle C,且 3 个 Bundle 的依赖关系如下:

  • Bundle A: 声明发布了 packageA,依赖了 java.* 的包;
  • Bundle B: 声明依赖了 packageA 和 packageC,同时也依赖了 java.* 的包;
  • Bundle C: 声明发布了 packageC,依赖了 packageA;

3 个 Bundle 之间的类加载器及父类加载器之间的关系如下:

请添加图片描述

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

若 Bundle A 依赖 Bundle B 的 Package B,Bundle B 依赖 Bundle A 的 Package A;在类加载时,首先加载 Bundle A 时会锁定 Bundle A 的类加载器实例,然后委派 Bundle B 的类加载器去加载 Bundle B;而此时加载 Bundle B 的线程也会锁定 Bundle B 的类加载器实例,再去请求 Bundle A 的 Package A,如此相互等待,就会形成死锁;(这个问题在 JDK 7 之前可以通过 osgi.classloader.singleTheadLoads 参数强制单线程串行进行类加载解决,但会损耗性能;在 JDK 7 之后才从 JDK 层面得到解决,将锁的级别从 ClassLoader 对象本身降到了要加载的类名级别)

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

JDK 中的 javac 是字节码技术的老祖宗,学习字节码的最好方式是阅读 javac 的源码(jdk.compiler/xhare/classes/com/sun/tools/javac);

使用字节码的场景有 Web 服务器中的 JSP 编译器、编译时织入的 AOP、动态代理、反射也可能通过运行时生成字节码提高执行速度;

动态代理,针对使用 Java 代码实现代理类的静态代理写法,动态代理在原始类和接口未知的情况下,实现代理类的行为,让代理类和原始类脱离了直接关系,从而让代理可以灵活的重用在不同场景;Spring 通过动态代理的方式对 Bean 进行增强;

动态代理示例

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();hello.toString();// 输出:// welcome// hello world// welcome}
}

Proxy::newProxyInstance() 方法返回一个实现了 IHello 的接口,并代理了 new Hello() 实例化的对象;其中程序进行了验证、优化、缓存、同步、生成字节码、显示类加载等;

设置保存运行时生成的代理类字节码文件

// 在 main() 中添加如下代码
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

可以得到 #Proxy0.class 的 Class 文件,通过反编译,可以得到动态代理类;

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package edu.aurelius.jvm.clazz;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 DynamicProxyTest.IHello {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public $Proxy0(InvocationHandler var1) throws  {super(var1);}public final boolean equals(Object var1) throws  {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final void sayHello() throws  {try {super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final String toString() throws  {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final int hashCode() throws  {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m3 = Class.forName("edu.aurelius.jvm.clazz.DynamicProxyTest$IHello").getMethod("sayHello");m2 = Class.forName("java.lang.Object").getMethod("toString");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}

这里 super.h 是父类 Proxy 中保存的 InvocationHandler 对象(DynamicProxy 对象),统一调用 super.h 对象的 invoke() 方法来实现代理方法,让 DynamicProxy 对象 的 invoke() 中插入的逻辑可以作用在所有被代理对象(new Hello() 实例化的对象)的方法上;

实际开发中以字节为单位拼接字节码的应用场景几乎是很少见的,在需要大量操作字节码时,建议使用封装好的字节码类库(Javassist、GCLib、ASM);

4. Backport 工具: Java 逆向移植

Java Backporting Tools,Java 逆向移植工具,如 Retrotranslator 和 Retrolambda;

  • Retrotranslator,将 JDK 5 编译出来的 Class 文件转变为可以在 JDK 1.4 或 1.3 上部署的版本,从而支持自动装箱、泛型、动态注解、枚举、变长参数、遍历循环、静态导入、集合改进、并发包、泛型与注解等的反射等语法特性;
  • Retrolambda,将 JDK 8 的 Lambda 表达式、try-resources、接口默认方法等语法转换为在 JDK 5、6、7 部署环境上支持的形式;

JDK 升级新增功能分类

  • 对 Java 类库 API 增强;如 JDK 1.2 引入的 java.util.Collections 集合包,JDK 5 引入的 java.util.concurrent 并发包,JDK 7 引入的 java.lang.invoke 包;
  • 对前端编译层面做改进,支持新的语法糖;如自动装箱拆箱(编译器自动对包装对象使用 Integer.valueOf() 等代码)、变成参数(编译后自动转化为数组)、泛型(编译阶段擦除泛型信息,在元数据中保存,编译器自动插入类型转换代码);
  • 在字节码中进行支持的改动;如 JDK 7 引入的动态语言支持,在 JVM 中新增了 invokedynamic 字节码指令;
  • JDK 整体结构层面的改进;如 JDK 9 引入的 Java 模块化系统,涉及 JDK 结构、Java 语法、类加载和连接过程、JVM 等各个层面;
  • JVM 内部的改进;如 JDK 5 定义的 Java 内存模型(Java Memory Model,JMM),JDK 7、JDK 11、JDK 12 中新增的 G1、ZGC、Shenandoah 收集器等;这类改动对 Java 应用程序是透明的,只会影响程序运行时;

逆向移植可以较好的模拟前 2 类升级,后 3 类则无能为力或影响运行效率;

对第 1 类升级的逆向移植可以通过独立类库代替 JDK 中部分功能的方式来实现;
对第 2 类升级需要使用 ASM 等字节码操作工具对字节码中元数据信息和一些语法支持的内容做相应修改;
对第 3 类升级则需要绕开字节码指令,牺牲了性能;如 lambda 表达式的逆向支持,其实是生成了一组匿名内部类代替 Lambda;IntelliJ IDEA 中将匿名内部类显示成 Lambda 表达式其实就是反向使用这个过程的效果;


上一篇:「JVM 执行引擎」栈架构的字节码的解释执行引擎

PS:感谢每一位志同道合者的阅读,欢迎关注、评论、赞!


参考资料:

  • [1]《深入理解 Java 虚拟机》

相关文章:

「JVM 原理使用」 实际开发中的应用

Class 文件格式、执行引擎主要以 Class 文件描述了存储格式、类何时加载、如何连接、VM 如何执行字节码指令,这些动作基本都是 JVM 直接控制,用户代码无法干预和改变; 用户可以干预的只有字节码生成、类加载器两部分,而这两部分的…...

最最普通程序员,如何利用工资攒够彩礼,成为人生赢家

今天我们不讲如何提升你的专业技能去涨工资,不讲面试技巧如何跳槽涨工资,不讲如何干兼职赚人生第一桶金,就讲一个最最普通的程序员,如何在工作几年后,可以攒够彩礼钱,婚礼酒席钱,在自己人生大事…...

脏话越多,代码越好!

你在读开源代码的时候有没有遇到过这种注释?What the fuck ?Dude,WTFFuck this !我遇到过,每次都忍不住笑,心想老外可真是性情中人,遇到不爽的地方就开骂,还直接写到注释中,甚至代码中。Bob大叔…...

【Node.js】模块化

模块化模块化的基本概念模块化规范Node.js中模块化分类模块作用域向外共享模块作用域的成员Node.js中的模块化规范模块化的基本概念 指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程对于整个系统来说,模块是可组合,分解和更换…...

训练一个中文gpt2模型

前言 这是我的github上的一个介绍,关于如何训练中文版本的gpt2的。链接为: https://github.com/yuanzhoulvpi2017/zero_nlp 介绍 本文,将介绍如何使用中文语料,训练一个gpt2可以使用你自己的数据训练,用来:写新闻、…...

python文件头规范和函数注释自动生成(pycharm)

#!/usr/bin/env python # -*- coding: utf-8 -*- """ Time : ${DATE} ${TIME} Author : xxx Email : xxxxxx.comFileName: ${NAME}.py Software: ${PRODUCT_NAME} """if __name__ __main__:print(Python)pycharm python文件头规范和函数注…...

Fluent Python 笔记 第 17 章 使用 future 处理并发

future 指一种对象,表示异步执行的操作。这个概念的作用很大,是 concurrent.futures 模块和 asyncio 包(第 18 章讨论)的基础。 17.1 示例:网络下载的三种风格 17.1.1 依序下载的脚本 17.1.2 使用 concurrent.futures 模块下载 from concurrent impo…...

Android进阶之路 - StringUtils、NumberUtils 场景源码

忘记是在去年还是前年的时候遇到一个需要检测所传字符串是否为数字的场景,开始使用 NumberUtils.isNumber() 提示错误 ,没有解决问题(可能是因为依赖版本导致),最后使用的是StringUtils.isNumeric(),当时关…...

装备制造业数字化转型CRM系统解决方案(信息图)

一、制造企业面临的机遇与挑战 2021年12月28日,工业和信息化部等八部门联合对外发布《“十四五”智能制造发展规划》,明确提到“推进智能制造,要立足制造本质,紧扣智能特征,以工艺、装备为核心,以数据为基…...

CGAL 二维剖分

目录一、 2D Triangulations1、定义2 Representation2.1 The Set of Faces2.2 A Representation Based on Faces and Vertices3 Software Design4 Basic Triangulations4.1 Description遍历三角网顶点4.2 Implementation4.3 Geometric Traits4.4 Example of a Basic Triangulat…...

node.js+vue婚纱影楼摄影婚庆管理系统vscode项目

:减少管理婚庆工作人员的负担;管理人员可以随时浏览婚纱网站以便及时知道哪里需要修改和更进,同时还可以查看用户反馈给我们的信息,让管理员更加直观的了解客户的需求;该系统改变了以前手工记录的方式,使用…...

C语言 指针的新理解

16年写了很多 C 与 C 相关的文章,但是后面从事了 Android 开发,就全部删掉了,无意中发现了这篇由还存在草稿箱,索性就找回来吧,也是追忆当年学习的青葱岁月 1.指针就是一个存储了其他变量地址的变量。 指针存储的是整…...

【向每个应用View中增加子控件 Objective-C语言】

一、把刚才计算九宫格的思路再给大家过一遍 1.现在我们要计算九宫格坐标 1)先把每一个格子,每一个九宫格的大小,先确定了, 在这里先指定宽和高 CGFloat appW = 75; CGFloat appH = 90; 2)再去计算第一个格子的一些间距, 到上面的间距,marginTop = 30; 再计算出…...

【FPGA】Verilog:组合电路设计 | 三输入 | 多数表决器

前言:本章内容主要是演示Vivado下利用Verilog语言进行电路设计、仿真、综合和下载的示例:表决器(三人表决器)。 功能特性: 采用 Xilinx Artix-7 XC7A35T芯片 配置方式:USB-JTAG/SPI Flash 高达100MHz 的内部…...

【安全等保】安全等保二级和三级哪个高?哪个费用更高?

等保政策已经严格落地执行了,各大企业纷纷接到了过等保的通知,但有的估计是第一次听到等保,对于等保相关政策都是非常蒙圈的。这不不少企业相关负责人在问,安全等保二级和三级哪个高?哪个费用更高?这里我们…...

C++ STL学习记录(v1)

C STL学习记录一. 什么是STL1.1 STL的诞生1.2 STL基本概念1.3 STL的六大组件1.4 STL中的容器、算法、迭代器1.5 容器、算法、迭代器实践一. 什么是STL 1.1 STL的诞生 STL建立的目的就是为了解决软件界复用性的需求。C的面向对象和泛型编程思想,目的就是为了复用性的…...

开发中遇到的问题

1.当写一个导出功能时,因为编码写URL地址&参数的时候,用反转字符串的时候换行了,造成地址拼接不成,一直报错,后来发现是编码格式造成的,已解决。 解决方案:不换行或者用 “”拼接 2.当本地…...

Javascript笔记

数据类型 基本类型(primitive value) 简单的数据段,包括 Undefined, Null, Boolean, Number, String初始化只使用2原始字面量形式,如果使用new则会创建Object无法加入新的属性 引用类型(reference value) 可能由多个值构成的对象判断类型 typeofinstanc…...

Elasticsearch(ES)配置及优化

在Elasticsearch中,索引的大小和存储能力取决于多个因素,包括文档大小、索引的分片数、硬件规格、查询负载和其他因素。索引和分片配置:索引和分片的数量和配置会对查询并发性能产生影响。如果索引和分片的数量太少,可能会导致查询…...

一文看懂Java语言与Java生态圈

Java语言与Java生态圈 1、Oracle JDK与Open JDK之间的关系 Oracle JDK Java最早是由SUN公司发明,Oracle JDK之前叫SUN JDK,显而易见,这是在2009年Oracle收购SUN公司之前,收购之后被名为Oracle JDK,实际上&#xff0…...

GitHub 上有什么嵌入式方面的项目?

原文直达,喜欢就点个赞吧! GitHub 上有什么嵌入式方面的项目? - CodeAllen的回答 - 知乎 https://www.zhihu.com/question/27835930/answer/2871624679 前言 对于GitHub,可能做互联网开发的同学会更加熟悉,尤其是前端&#xff0…...

【C语言进阶】结构体、位段、枚举和联合

👦个人主页:Weraphael ✍🏻作者简介:目前是C语言学习者 ✈️专栏:C语言航路 🐋 希望大家多多支持,咱一起进步!😁 如果文章对你有帮助的话 欢迎 评论💬 点赞&a…...

markdown和latex常用部分参考@注脚@链接跳转@csdn

文章目录refmarkdown和latex常用部分参考typora文档基础语法扩展语法链接内联链接的方式将链接提取出来链接示例typora的支持LinksInline LinksInternal Links🎈Reference LinksURLs文章内部跳转(Heading IDs)🎈My Great Heading注脚(Footnotes)&#x1…...

Java 在二叉树中增加一行

623. 在二叉树中增加一行中等给定一个二叉树的根 root 和两个整数 val 和 depth ,在给定的深度 depth 处添加一个值为 val 的节点行。注意,根节点 root 位于深度 1 。加法规则如下:给定整数 depth,对于深度为 depth - 1 的每个非空树节点 cur…...

kubernetes(k8s) 知识总结(第2期)

1. “控制器”思想 kube-controller-manager 是一系列控制器的集合,这些控制器被放在 Kubernetes 项目的 pkg/controller 目录,这些控制器都以独有的方式负责某种编排功能。它们都遵循一个通用的编排模式——控制循环。 以 Deployment 为例介绍它对控…...

windows-Mysql的主从数据库同步设置

复制原有的mysql修改my.ini配置文件 修改端口号修改从数据的地址和从数据库的数据存放地址安装从数据库进入从数据库的bin目录,打开命令窗口输入命令:mysqld.exe install mysql-back --defaults-file "C:\ProgramData\MySQL\MySQL Server 5.7-back\…...

Docker逃逸

文章目录原理环境搭建Docker 环境判断Docker 容器逃逸特权模式逃逸如何判断是否为特权模式逃逸docker.sock挂载逃逸逃逸Remote API未授权访问未授权访问逃逸容器服务缺陷逃逸影响版本环境搭建逃逸脏牛漏洞逃逸参考原理 docker其实就是一个linux下的进程,它通过Name…...

k8s项目部署

k8s命令k8s项目部署部署流程实现导出相应的yaml文件 kubectl create deployment 名字--image镜像-o yaml --dry-runclient > 文件名 例: kubectl create deployment nginx --imagenginx -o yaml --dry-runclient > m1.yaml导出已经部署后的yaml文件 kubectl g…...

Modbus通信协议学习笔记

Modbus主从设备 主控设备(Modbus Master):工控机、PLC、触摸屏等等 从设备(Modbus Slave):PLC、Modbus采集模块、带485通讯的传感器、仪器仪表等等 Modbus物理接口:串口(RS232、RS4…...

ubuntu重启、关机命令

// // // //之前用linux系统, 一键解决也是可以的,反正我每次用命令(泪目…),中间崩了好几次,换回win,此篇也做记录 // // // 重启命令 以下所有命令在root根目录下输入(普通用户&…...