当前位置:首页 > Java技术 > JVM SandBox简要介绍

JVM SandBox简要介绍

2022年08月06日 07:23:36Java技术6

JVM-SANDBOX(沙箱)实现了一种在不重启、不侵入目标JVM应用的AOP解决方案。

沙箱的特性

  1. 无侵入:目标应用无需重启也无需感知沙箱的存在
  2. 类隔离:沙箱以及沙箱的模块不会和目标应用的类相互干扰
  3. 可插拔:沙箱以及沙箱的模块可以随时加载和卸载,不会在目标应用留下痕迹
  4. 多租户:目标应用可以同时挂载不同租户下的沙箱并独立控制
  5. 高兼容:支持JDK[6,11]

沙箱常见应用场景

  • 线上故障定位
  • 线上系统流控
  • 线上故障模拟
  • 方法请求录制和结果回放
  • 动态日志打印
  • 安全信息监测和脱敏

 

1.1 AOP

在介绍 JVM SandBox 之前,我们先来回顾一下 AOP 技术。

AOP(面向切面编程,Aspect Oriented Programming)技术已被业界广泛应用,其思想是面向业务处理过程的某个步骤或阶段进行编程,这个步骤或阶段被称为切面,其目的是降低业务逻辑的各部分之间的耦合,常见的 AOP 实现基本原理有两种:代理和行为注入。

1)代理模式
在代理模式下,我们会创建一个代理对象来代理原对象的行为,代理对象拥有原对象行为执行的控制权,在这种模式下,我们基于代理对象在原对象行为执行的前后插入代码来实现 AOP。

JVM SandBox简要介绍 _ JavaClub全栈架构师技术笔记

2-1 代理模式

2)行为注入模式
在行为注入模式下,我们不会创建一个新的对象,而是修改原对象,在原对象行为的执行前后注入代码来实现 AOP。

JVM SandBox简要介绍 _ JavaClub全栈架构师技术笔记

2-2 注入模式

1.2 JVM SandBox

   JVM SandBox 是阿里开源的一款 JVM 平台非侵入式运行期 AOP 解决方案,本质上是一种 AOP 落地形式。

   为什么不采用 Spring AOP 方案呢?Spring AOP 方案的痛点在于不是所有业务代码都托管在 Spring 容器中,而且更底层的中间件代码、三方包代码无法纳入到回归测试范围,更糟糕的是测试框架会引入自身所依赖的类库,经常与业务代码的类库产生冲突,因此,JVM SandBox 应运而生。

   JVM SandBox 本身是基于插件化的设计思想,允许用于以“模块”的方式基于 JVM SandBox 提供的 AOP 能力开发新的功能。基于 JVM SandBox,我们不需要关心如何在 JVM 层实现 AOP 的技术细节,只需要通过 JVM SandBox 提供的编程结构告诉“沙箱”,我们希望对哪些类哪些方法进行 AOP,在切面点做什么即可,JVM SandBox 模块功能编写起来非常简单。下面是一个示例模块代码:

@MetaInfServices(Module.class)  
@Information(id = "my-sandbox-module")// 模块名  
public class MySandBoxModule implements Module {  
    private Logger LOG = Logger.getLogger(MySandBoxModule.class.getName());  
    @Resource  
    private ModuleEventWatcher moduleEventWatcher;  
  
    @Command("addLog")// 模块命令名  
    public void addLog() {  
        new EventWatchBuilder(moduleEventWatcher)  
                .onClass("com.float.lu.DealGroupService")// 想要对 DealGroupService 这个类进行切面  
                .onBehavior("loadDealGroup")// 想要对上面类的 loadDealGroup 方法进行切面  
                .onWatch(new AdviceListener() {  
                    @Override  
                    protected void before(Advice advice) throws Throwable {  
                        LOG.info(" 方法名: " + advice.getBehavior().getName());// 在方法执行前打印方法的名字  
                    }  
                });  
    }  
}  

 

 

如上面代码所示,通过简单常规的编码即可实现对某个类的某个方法进行切面,不需要有了解即可上手。上面的模 JVM SandBox 和初始化之后便可以被使用了。比如,只需要告 JVM SandBox my-sandbox-module 这个模块的 addLog 这个方法,我们编写的功能的调用就会被注入到目标地方

 

我们知道Java对象的行为(函数,方法)是存储在方法区的,从下图可以看到,方法区的数据是由类加载器把编译好的class文件加载到jvm方法区的。所以我们可以得出简单思路是:
1. 在对应类Java代码中新增日志代码,并重新编译得到新的class文件。
2. 让jvm重新加载这个类的class文件到方法区

JVM SandBox简要介绍 _ JavaClub全栈架构师技术笔记

第一步倒是挺好实现,但是第二步,如何让jvm加载一个已经加载过的类?
答案是“java.lang.instrument.Instrumentation”

instrument JVM 提供的一个可以修改已加载类的类库,专门为 Java 语言编 写的插桩服务提供支持。Instrumentation中有两个方法都可以实现重新替换已经存在的class文件,它们是:redefineClasses retransformClasses。区别是redefineClasses 是自己提供字节码文件替换 掉已存在的 class 文件,retransformClasses 是在已存在的字节码文件上修改后再替换之。它需要依赖 JVMTI Attach API 机制实现。JVM TI(JVM TOOL INTERFACEJVM 工具接口) JVM 提供的一套对 JVM 进行操作的工具接口。通过JVMTI,可以实现对 JVM 的多种操作,它通过接口注册 各种事件勾子,在 JVM 事件触发时,同时触发预定义的勾子,以实现对各个 JVM 事件的响应,事件包括类文件加载、异常产生与捕获、线程启动和结束、进入和退 出临界区、成员变量修改、GC开始和结束、方法调用进入和退出、临界区竞争与等 待、VM 启动与退出等等。Instrument 就是一个基于 JVMTI 接口的,以代理方式连接和访问 JVM 的一个 Agent

 

JVM SandBox 容器的启动依赖 Java Agent,Java Agent(Java 代理)是 JDK 1.5 之后引入的技术。Agent 就是 JVMTI 的一种实现,Agent 有两种启动方式,一是随 Java 进 程启动而启动;二是运行时载入,通过 attach API,将模块(jar 包)动态地 Attach 到指定进程 id 的 Java 进程内。开发一个 Java Agent 有两种方式,一种是实现一个 premain 方法,但是这种方式实现的 Java Agent 只能在 JVM 启动的时候被加载;另一种是实现一个 agentmain 方法,这种方式实现的 Java Agent 可以在 JVM 启动之后被加载。JVM SandBox Agent 对于这两种方式都有实现,用户可以自行选择使用,因为在 JVM 层这两种方式底层的实现原理大同小异,下面先通过两行代码,来看看基于 agentmain 方式实现的 Java Agent 是如何被加载的:

VirtualMachine vmObj = VirtualMachine.attach(targetJvmPid);//targetJvmPid 为目标 JVM 的进程 ID 

vmObj.loadAgent(agentJarPath, cfg);  // agentJarPath agent jar 包的路径,cfg 为传递给 agent 的参数 

1.3可插拔

本文理解的 JVM SandBox 可插拔至少有两层含义:一层是 JVM 沙箱本身是可以被插拔的,可被动态地挂载到指定 JVM 进程上和可以被动态地卸载;另一层是 JVM 沙箱内部的模块是可以被插拔的,在沙箱启动期间,被加载的模块可以被动态地启用和卸载。
一个典型的沙箱使用流程如下:

$./sandbox.sh -p 33342 #将沙箱挂载到进程号为 33342 的 JVM 进程上 

$./sandbox.sh -p 33342 -d 'my-sandbox-module/addLog' #运行指定模块, 模块功能生效 

$./sandbox.sh -p 33342 -S #卸载沙箱 

JVM 沙箱可以被动态地挂载到某个正在运行的目标 JVM 进程之上(前提是目标 JVM 没有禁止 attach 功能),沙箱工作完之后还可以被动态地从目标 JVM 进程卸载掉,沙箱被卸载之后,沙箱对对目标 JVM 进程产生的影响会随即消失(这是沙箱的一个重要特性),沙箱工作示意图如下:

JVM SandBox简要介绍 _ JavaClub全栈架构师技术笔记

4-1 沙箱工作示意

客户端通过 Attach 将沙箱挂载到目标 JVM 进程上,沙箱的启动实际上是依赖 Java Agent,上文已经介绍过,启动之后沙箱会一直维护着 Instrument 对象引用,在沙箱中 Instrument 对象是一个非常重要的角色,它是沙箱访问和操作 JVM 的唯一通道,后续修改字节码和重定义类都要经过 Instrument。另外,沙箱启动之后同时会启动一个内部的 Jetty 服务器,这个服务器用于外部进程和沙箱进行通信,上面看到的./sandbox.sh -p 33342 -d ‘my-sandbox-module/addLog’ 这行代码,实际上就是通过 HTTP 协议来告诉沙箱执行 my-sanbox-module 这个模块的 addLog 这个功能的。

 

1.4 

sandbox的代主要分几个程:启、模强实现

启动

上面我们提到,使用Instrumentation进行字节码增强有2种模式(attach模式和java-agent模式),sandbox-jvm的启动有这2种方式,入口都在AgentLauncher中,分别对应着agentmain和premain,它们都调用了install方法,以agentmain为例

public static void agentmain(String featureString, Instrumentation inst) {
        LAUNCH_MODE = LAUNCH_MODE_ATTACH;
        final Map<String, String> featureMap = toFeatureMap(featureString);
        writeAttachResult(
                getNamespace(featureMap),
                getToken(featureMap),
                install(featureMap, inst)
        );
    }

install函数的作用是在目标jvm上安装sandbox,创建独立的classloader,通过classloader加载JettyCoreServer.class,并且反射生成实例,建立httpserver监听请求

// CoreServer类定义
final Class<?> classOfProxyServer = sandboxClassLoader.loadClass(CLASS_OF_PROXY_CORE_SERVER);
// 获取CoreServer单例
final Object objectOfProxyServer = classOfProxyServer
		.getMethod("getInstance")
		.invoke(null);
// CoreServer.isBind()
final boolean isBind = (Boolean) classOfProxyServer.getMethod("isBind").invoke(objectOfProxyServer);
// 如果未绑定,则需要绑定一个地址
if (!isBind) {
	try {
		classOfProxyServer
				.getMethod("bind", classOfConfigure, Instrumentation.class)
				.invoke(objectOfProxyServer, objectOfCoreConfigure, inst);
	} catch (Throwable t) {
		classOfProxyServer.getMethod("destroy").invoke(objectOfProxyServer);
		throw t;
	}
}

启动jetty server,监听http请求,并且调用coreModuleManager.reset进行模块的加载,在下面一节介绍。

public synchronized void bind(final CoreConfigure cfg, final Instrumentation inst) throws IOException {
        this.cfg = cfg;
        try {
            initializer.initProcess(()->{
                    logger.info("initializing server. cfg={}", cfg);
                    jvmSandbox = new JvmSandbox(cfg, inst);
                    initHttpServer();
                    initJettyContextHandler();
                    httpServer.start();
                }
            });
            // 初始化加载所有的模块
            try {
                jvmSandbox.getCoreModuleManager().reset();
            } catch (Throwable cause) {
                logger.warn("reset occur error when initializing.", cause);
            }
            final InetSocketAddress local = getLocal();
            logger.info("initialized server. actual bind to {}:{}",
                    local.getHostName(),
                    local.getPort()
            );
        } catch (Throwable cause) {
                     // 对外抛出到目标应用中
            throw new IOException("server bind failed.", cause);
        }
    }

模块加载

模块是什么?sandbox将不同的业务进行模块划分,不同的模块使用不同的classloader进行加载,例如如果我们想实现流量录制,我们可以自定义一个模块通过字节码增强实现流量入口的监听并进行录制,这就是我们后面会介绍的repeater。
先来看下CoreModuleManager.reset() 的工作:
加载过程是先卸载再加载,首先根据cfg(配置存储对象)中的的module包路径配置得到moduleLibDirArray(需要加载的模块路径:系统模块+用户模块), 每个模块独立加载。

for (final File moduleLibDir : moduleLibDirArray) {
	// 用户模块加载目录,加载用户模块目录下的所有模块
	// 对模块访问权限进行校验
	if (moduleLibDir.exists() && moduleLibDir.canRead()) {
		new ModuleLibLoader(moduleLibDir, cfg.getLaunchMode())
				.load(new InnerModuleJarLoadCallback(), new InnerModuleLoadCallback());
	} 
}  

通过阅读sandbox.sh,主要功能分为两个:

  1. attach目标JVM
  2. 发送HTTP请求,控制jvm-sandbox的运行

由于我需要在远程控制JVM-Sandbox,,而attach必须在目标主机上运行,所以我主要关注如何发送HTTP请求。

HTTP请求的格式如下:

http://${host}:${port}/sandbox/${namespace}/module/http/${module-name}/${command-name}?V1=K1&...

${host} 为JVM-Sandbox运行的节点

${port} 为JVM-Sadnbox监听的端口

${namespace} 默认为default,用于区分不同的JVM-Sandbox

${module-name} 是实现Module时,@Information注解的值

${command-name} 是定义Module方法是,@Command注解的值

利用上述URL,我们就可以远程操控JVM-Sandbox了。

 

 

 

 

参考

https://blog.csdn.net/weixin_37512224/article/details/108226345

作者:鹏城周
来源链接:https://blog.csdn.net/weixin_36996888/article/details/109098779

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/java/18426.html

分享给朋友:

“JVM SandBox简要介绍” 的相关文章

IDEA启动失败,报Failed to create JVM错误的解决办法

IDEA启动失败,报Failed to create JVM错误的解决办法

IDEA启动失败,报Failed to create JVM错误的解决办法 从网上下载的破解版IDEA,照网上的pojie方法进行破解时,很多人是用Windows自带的记事本添加并保存的。但是用记事本保存的文件是带有BOM的,从而导致启动IDEA报...

[JVM教程与调优] 为什么要学习JVM虚拟机?

[JVM教程与调优] 为什么要学习JVM虚拟机?

JVM在我们开发阶段不会用到,但是到了生产环境中,那么就会变得非常重要了。 为什么这么说呢? 一方面,因为我们的生产环境是比较复杂的。各种可能的问题都会出现,比如说:硬盘坏了、网络坏了、CPU利用率高了等问题层次不穷。 另外一方面,在我们生产环境出现问题,还不好进行定位。因为没...

Java String(JVM角度)

Java String(JVM角度)

基本特性 存储结构变更 jdk8及之前的jdk版本中,String的内存存储结构是char[]字符数组,但是在Jdk9及之后改成了byte[]字节数组。 原因是,堆空间中大部分的字符串内容都是latin字符,基本上...

JVM 从入门到精通(二)JVM和Java体系结构

JVM 从入门到精通(二)JVM和Java体系结构

写在前面:我是「云祁」,一枚热爱技术、会写诗的大数据开发猿。昵称来源于王安石诗中一句 [ 云之祁祁,或雨于渊 ] ,甚是喜欢。 写博客一方面是对自己学习的一点点总结及记录,另一方面则是希望能够帮助更多对大数据感兴趣的朋友。如果你也对 数据中台...

JVM中有哪些垃圾收集器?

写在前面 本文隶属于专栏《100个问题搞定Java虚拟机》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和文献引用请见100个问题搞定Java虚拟机 解答 新生代垃圾...

JVM内存模型及垃圾收集策略解析

JVM内存模型及垃圾收集策略解析

JVM内存模型是Java的核心技术之一,之前51CTO曾为大家介绍过JVM分代垃圾回收策略的基础概念,现在很多编程语言都引入了类似Java JVM的内存模型和垃圾收集器的机制,下面我们将主要针对Java中的JVM内存模型及垃圾收集的具体策略进行综合的分析。 一 JVM内存模型...

浅谈JVM内存模型

浅谈JVM内存模型

JAVA虚拟机在执行JAVA程序的时候,会把它管理的内存分成若干不同的数据区域,每个区域都有各自的用途。目前大致把JVM内存模型划分为五个区域:程序计数器,虚拟机栈,本地方法栈,堆和方法区。   程序计数器 程序计数器(ProgramCounterR...

深入理解JVM—JVM内存模型

深入理解JVM—JVM内存模型

原文地址:http://yhjhappy234.blog.163.com/blog/static/316328322011101723933875/?suggestedreading&wumii 我们知道,计算机CPU和内存的交互是最频繁的,内存是我们的高速缓存区,用户磁...

jvm性能调优实战 - 31从测试到上线

jvm性能调优实战 - 31从测试到上线

文章目录 Pre 开发好系统之后的预估性优化 系统压测时的JVM优化 对线上系统进行JVM监控 Pre 前面两篇文章,已经给大家介绍...

JVM学习笔记1:Java虚拟机内存模型

JVM学习笔记1:Java虚拟机内存模型

JVM学习笔记1:Java虚拟机内存模型 学习JVM,Java虚拟机对理解Java程序执行过程和Java程序性能调优具有很大帮助。本系列博客旨在由浅到深学习并理解JVM。参考阅读:《深入理解Java虚拟机-JVM高级特性和最佳实践》。这个书写的非常好,推荐有条件的读者买一本来阅读...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。