AOP—JVM SandBox—快速上手

原文作者:stingfire

原文地址:深入学习jvm-sandbox(安装&快速上手)

目录

一、安装

1. 下载

2. 运行安装脚本

3. 阅读安装脚本

3.1 首先是定义安装目录变量

3.2 解析参数

3.3 拷贝文件

3.4 更新sandbox.sh脚本

3.5 输出结果

二、快速上手

2.1 ATTACH方式启动

2.2 AGENT方式启动

2.3 实操(wiki例子:修复一个损坏了的钟)

2.3.1 创建Clock类

2.3.2 创建修复Clock的模块(module)

2.3.3 部署模块

2.3.4 启动sandbox

2.3.5 执行修复


一、安装

jvm-sandbox的安装非常简单,简言之就是执行下载文件夹里的install-local.sh,下载地址请访问这里

1. 下载

下载安装包的zip文件解压后目录结构如下:

这里面bin目录下的sandbox.sh就是jvm-sandbox交互的命令行脚本,但是没有安装前不能直接使用,因为还有一些变量没有定义。example、module、provider目录下的各种jar文件就是各种用途的module文件(什么是module以后再做介绍,这里理解成一个个插件即可)。cfg目录下是sandbox的配置文件,lib目录下的jar文件是sandbox的核心运行逻辑,以后再详述。install-local.sh就是我们需要执行的安装文件咯,保障它有执行权限并运行。

2. 运行安装脚本

install-local.sh脚本运行的参数很简单:

  • -h : 帮助选项,打印命令帮助信息。
  • -p : 指定本地安装目录。如果不指定,则默认安装在"${HOME}/.opt"下,也就是当前用户主目录下的 ~/.opt/ 下面。

脚本执行完成后,输出如下信息表明执行成功。

至此,sandbox安装完毕,可以开始你的各种花式玩耍来练手了。SO EASY!!!

3. 阅读安装脚本

打开解压安装包里的install-local.sh,可以发现安装逻辑还是简单的。我们这里只介绍主要逻辑:

3.1 首先是定义安装目录变量

定义安装目录变量并且赋默认值,也就是前面说的如果不指定“-p“参数的默认安装目录。

typeset SANDBOX_INSTALL_PREFIX

typeset DEFAULT_SANDBOX_INSTALL_PREFIX="${HOME}/.opt"

 

3.2 解析参数

跳过辅助方法exit_on_err()和usage() ,我们直接看main()。这个直接用系统方法getopts解析参数,-h则打印帮助信息并退出,-p则指定默认值。

while getopts "hp:" ARG
    do
        case ${ARG} in
            h)
                usage
                exit
            ;;
            p)
                SANDBOX_INSTALL_PREFIX=${OPTARG}
            ;;
        esac
    done
 
 
    # if not appoint the install local, default is ${HOME}/.opt
    if [[ -z ${SANDBOX_INSTALL_PREFIX} ]]; then
        SANDBOX_INSTALL_PREFIX=${DEFAULT_SANDBOX_INSTALL_PREFIX}
    fi

3.3 拷贝文件

接下来根据指定的(或者默认的)目录地址创建文件夹,并将lib、module、provider下的文件拷贝到安装目录下。

# create install dir
    mkdir -p ${SANDBOX_INSTALL_LOCAL} \
        || exit_on_err 1 "permission denied, create ${SANDBOX_INSTALL_LOCAL} failed."
 
    # copy file
    cp -r ./cfg ${SANDBOX_INSTALL_LOCAL}/ \
        && cp -r ./lib ${SANDBOX_INSTALL_LOCAL}/ \
        && cp -r ./module ${SANDBOX_INSTALL_LOCAL}/ \
        && cp -r ./provider ${SANDBOX_INSTALL_LOCAL}/ \
        && mkdir -p ${SANDBOX_INSTALL_LOCAL}/bin \
        || exit_on_err 1 "permission denied, copy file failed."

3.4 更新sandbox.sh脚本

 

 

# replace sandbox.sh\`s ${SANDBOX_HOME_DIR}
    cat ./bin/sandbox.sh \
        | sed "s:typeset SANDBOX_HOME_DIR:typeset SANDBOX_HOME_DIR=${SANDBOX_INSTALL_LOCAL}:g" \
        > ${SANDBOX_INSTALL_LOCAL}/bin/sandbox.sh \
        && chmod +x ${SANDBOX_INSTALL_LOCAL}/bin/sandbox.sh \
        || exit_on_err 1 "permission denied, replace ${SANDBOX_INSTALL_LOCAL}/bin/sandbox.sh failed."

3.5 输出结果

最后保存sandbox版本信息,并打印输出sandbox相关信息:VERSION、PATH和安装sandbox成功的信息,也就是前面说的判断成功的信息。

# got sandbox's version
local SANDBOX_VERSION=$(cat ${SANDBOX_INSTALL_LOCAL}/cfg/version)
 
echo "VERSION=${SANDBOX_VERSION}"
echo "PATH=${SANDBOX_INSTALL_LOCAL}"
echo "install sandbox successful."

二、快速上手

安装好sandbox后就可以使用了,使用方法也很简单,有两种执行方式:ATTACH和AGENT两种方式。前者将sandbox attach到指定java进程上;后者在启动java虚拟机的时候直接指定参数。熟悉java agent的朋友应该知道agent的agentmain和premain两种方式,这里咱们暂时留个伏笔。就笔者个人来说主要接触ATTACH方式,在java程序启动后动态加载并改变程序行为。

2.1 ATTACH方式启动

不用重启目标程序,直接attach上去执行操作。通过ps命令获得目标程序的进程ID(例子中假设为47625),则执行命令:

# 假设目标JVM进程号为'47625'
./sandbox.sh -p 47625

结果输出如下信息表明启动成功:

2.2 AGENT方式启动

如果ATTACH方式启动会影响性能,因为毕竟加载sandbox需要额外的开销,则可以在程序启动时添加JVM参数启动:

-javaagent:/Path_to_Install_Directory/sandbox/lib/sandbox-agent.jar

/Path_to_Install_Directory是sandbox的安装目录。

2.3 实操(wiki例子:修复一个损坏了的钟)

官方文档:https://github.com/alibaba/jvm-sandbox/wiki/FIRST-MODULE

2.3.1 创建Clock类

作为实验对象的Clock类会循环抛出IllegalStateException异常,实验目的就是通过一个用户自定义的module对该运行中的Clock进行热修复,使得程序不再抛出IllegalStateException。官方例子代码如下:

package com.taobao.demo;
 
/**
 * 报时的钟
 */
public class Clock {
 
    // 日期格式化
    private final java.text.SimpleDateFormat clockDateFormat
            = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
    /**
     * 状态检查
     */
    final void checkState() {
        throw new IllegalStateException("STATE ERROR!");
    }
 
    /**
     * 获取当前时间
     *
     * @return 当前时间
     */
    final java.util.Date now() {
        return new java.util.Date();
    }
 
    /**
     * 报告时间
     *
     * @return 报告时间
     */
    final String report() {
        checkState();
        return clockDateFormat.format(now());
    }
 
    /**
     * 循环播报时间
     */
    final void loopReport() throws InterruptedException {
        while (true) {
            try {
                System.out.println(report());
            } catch (Throwable cause) {
                cause.printStackTrace();
            }
            Thread.sleep(1000);
        }
    }
 
    public static void main(String... args) throws InterruptedException {
        new Clock().loopReport();
    }
 
}

编译运行后程序滚动抛出如下异常:

java.lang.IllegalStateException: STATE ERROR!
    at com.taobao.demo.Clock.checkState(Clock.java:16)
    at com.taobao.demo.Clock.report(Clock.java:34)
    at com.taobao.demo.Clock.loopReport(Clock.java:44)
    at com.taobao.demo.Clock.main(Clock.java:53)
java.lang.IllegalStateException: STATE ERROR!
    at com.taobao.demo.Clock.checkState(Clock.java:16)
    at com.taobao.demo.Clock.report(Clock.java:34)
    at com.taobao.demo.Clock.loopReport(Clock.java:44)
    at com.taobao.demo.Clock.main(Clock.java:53)
java.lang.IllegalStateException: STATE ERROR!
    at com.taobao.demo.Clock.checkState(Clock.java:16)
    at com.taobao.demo.Clock.report(Clock.java:34)
    at com.taobao.demo.Clock.loopReport(Clock.java:44)
    at com.taobao.demo.Clock.main(Clock.java:53)

2.3.2 创建修复Clock的模块(module)

创建一个Java工程clock-tinker,并且将parent指向sandbox-module-starter:

 <parent>
    <groupId>com.alibaba.jvm.sandbox</groupId>
    <artifactId>sandbox-module-starter</artifactId>
    <version>1.2.2</version>
 </parent>

笔者由于在github上下载了sandbox的源代码,所以直接在以sandbox-module-starter为parent的工程sandbox-mgr-module中创建类BrokenClockTinkerModule:

package com.alibaba.jvm.sandbox.demo;
 
import com.alibaba.jvm.sandbox.api.Information;
import com.alibaba.jvm.sandbox.api.Module;
import com.alibaba.jvm.sandbox.api.ProcessController;
import com.alibaba.jvm.sandbox.api.annotation.Command;
import com.alibaba.jvm.sandbox.api.listener.ext.Advice;
import com.alibaba.jvm.sandbox.api.listener.ext.AdviceListener;
import com.alibaba.jvm.sandbox.api.listener.ext.EventWatchBuilder;
import com.alibaba.jvm.sandbox.api.resource.ModuleEventWatcher;
import org.kohsuke.MetaInfServices;
 
import javax.annotation.Resource;
 
@MetaInfServices(Module.class)
@Information(id = "broken-clock-tinker")
public class BrokenClockTinkerModule implements Module {
 
    @Resource
    private ModuleEventWatcher moduleEventWatcher;
 
    @Command("repairCheckState")
    public void repairCheckState() {
 
        new EventWatchBuilder(moduleEventWatcher)
                .onClass("com.taobao.demo.Clock")
                .onBehavior("checkState")
                .onWatch(new AdviceListener() {
 
                    /**
                     * 拦截{@code com.taobao.demo.Clock#checkState()}方法,当这个方法抛出异常时将会被
                     * AdviceListener#afterThrowing()所拦截
                     */
                    @Override
                    protected void afterThrowing(Advice advice) throws Throwable {
                        
                        // 在此,你可以通过ProcessController来改变原有方法的执行流程
                        // 这里的代码意义是:改变原方法抛出异常的行为,变更为立即返回;void返回值用null表示
                        ProcessController.returnImmediately(null);
                    }
                });
 
    }
 
}

代码中的注解和代码含义大家先不用在意,能猜透哪些算哪些,接下来我们要把module代码部署成可用的module。

2.3.3 部署模块

部署模块就是把刚才编写的BrokenClockTinkerModule类打包成jar包,然后复制该jar包到sandbox安装目录下module/下。首先在工程pom文件所在目录执行命令:

mvn clean package

然后将打包的sandbox-mgr-module.jar(笔者是直接在sandbox-mgr-module工程中添加的BrokenClockTinkerModule,具体jar包名称根据pom配置定)文件复制到sandbox安装目录下的module目录下。

cp target/sandbox-mgr-module.jar /Path_to_Install_Dir/sandbox/module/sandbox-mgr-module.jar

2.3.4 启动sandbox

现在有了实验对象Clock,也部署了module,接下来启动sandbox(2.1部分ATTACH方式启动)。

./sandbox.sh -p 47625

当看到2.1部分所述的信息时表示挂载成功,可以进一步通过命令验证编写的模块正常加载:

./sandbox.sh -p 47625 -l

2.3.5 执行修复

module启动后,执行下面命令调用模块方法进行修复:

 ./sandbox.sh -p 47625 -d 'broken-clock-tinker/repairCheckState'

该命令执行broken-clock-tinker模块下repairCheckState方法,执行后之前的Clock程序输出变为:

java.lang.IllegalStateException: STATE ERROR!
	at com.example.demo.jvmsandbox.Clock.checkState(Clock.java:12)
	at com.example.demo.jvmsandbox.Clock.report(Clock.java:30)
	at com.example.demo.jvmsandbox.Clock.loopReport(Clock.java:40)
	at com.example.demo.jvmsandbox.Clock.main(Clock.java:49)
java.lang.IllegalStateException: STATE ERROR!
	at com.example.demo.jvmsandbox.Clock.checkState(Clock.java:12)
	at com.example.demo.jvmsandbox.Clock.report(Clock.java:30)
	at com.example.demo.jvmsandbox.Clock.loopReport(Clock.java:40)
	at com.example.demo.jvmsandbox.Clock.main(Clock.java:49)
2019-12-17 18:37:07
2019-12-17 18:37:08
2019-12-17 18:37:09
2019-12-17 18:37:10
2019-12-17 18:37:11

这口破损的钟被修复了!!!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值