原文作者:stingfire
目录
一、安装
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
这口破损的钟被修复了!!!