Skip to content
This repository has been archived by the owner on Jun 26, 2022. It is now read-only.

Commit

Permalink
add mc-redefine command scenario. alibaba#847
Browse files Browse the repository at this point in the history
Signed-off-by: hollowman6 <hollowman186@vip.qq.com>
  • Loading branch information
HollowMan6 committed Jul 26, 2020
1 parent c0b3272 commit 1172ab6
Show file tree
Hide file tree
Showing 20 changed files with 491 additions and 0 deletions.
2 changes: 2 additions & 0 deletions site/src/site/sphinx/en/mc.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
mc
===

[`mc-redefine` online tutorial](https://alibaba.github.io/arthas/arthas-tutorials?language=en&id=command-mc-redefine)

> Memory compiler, compiles `.java` files into `.class` files in memory.
```bash
Expand Down
2 changes: 2 additions & 0 deletions site/src/site/sphinx/en/redefine.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
redefine
========

[`mc-redefine` online tutorial](https://alibaba.github.io/arthas/arthas-tutorials?language=en&id=command-mc-redefine)

> Load the external `*.class` files to re-define the loaded classes in JVM.
Reference: [Instrumentation#redefineClasses](https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html#redefineClasses-java.lang.instrument.ClassDefinition...-)
Expand Down
2 changes: 2 additions & 0 deletions site/src/site/sphinx/mc.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
mc
===

[`mc-redefine`在线教程](https://alibaba.github.io/arthas/arthas-tutorials?language=cn&id=command-mc-redefine)

> Memory Compiler/内存编译器,编译`.java`文件生成`.class`
```bash
Expand Down
2 changes: 2 additions & 0 deletions site/src/site/sphinx/redefine.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
redefine
===

[`mc-redefine`在线教程](https://alibaba.github.io/arthas/arthas-tutorials?language=cn&id=command-mc-redefine)

> 加载外部的`.class`文件,redefine jvm已加载的类。
参考:[Instrumentation#redefineClasses](https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html#redefineClasses-java.lang.instrument.ClassDefinition...-)
Expand Down
16 changes: 16 additions & 0 deletions tutorials/katacoda/command-mc-redefine-cn/arthas-boot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@




在新的`Terminal 2`里,下载`arthas-boot.jar`,再用`java -jar`命令启动:

`wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar`{{execute T2}}

`arthas-boot``Arthas`的启动程序,它启动后,会列出所有的Java进程,用户可以选择需要诊断的目标进程。

选择第一个进程,输入 `1`{{execute T2}} ,再`Enter/回车`

Attach成功之后,会打印Arthas LOGO。输入 `help`{{execute T2}} 可以获取到更多的帮助信息。

![Arthas Boot](/arthas/scenarios/common-resources/assets/arthas-boot.png)
85 changes: 85 additions & 0 deletions tutorials/katacoda/command-mc-redefine-cn/case-jad-mc-redefine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
下面介绍通过`jad`/`mc`/`redefine` 命令实现动态更新代码的功能。

目前,访问 http://localhost/user/0 ,会返回500异常:

`curl http://localhost/user/0`{{execute T3}}

```
{"timestamp":1550223186170,"status":500,"error":"Internal Server Error","exception":"java.lang.IllegalArgumentException","message":"id < 1","path":"/user/0"}
```

下面通过热更新代码,修改这个逻辑。

### jad反编译UserController

`jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java`{{execute T2}}

jad反编译的结果保存在 `/tmp/UserController.java`文件里了。

再打开一个`Terminal 3`,然后用vim来编辑`/tmp/UserController.java`

`vim /tmp/UserController.java`{{execute T3}}

比如当 user id 小于1时,也正常返回,不抛出异常:

```java
@GetMapping(value={"/user/{id}"})
public User findUserById(@PathVariable Integer id) {
logger.info("id: {}", (Object)id);
if (id != null && id < 1) {
return new User(id, "name" + id);
// throw new IllegalArgumentException("id < 1");
}
return new User(id.intValue(), "name" + id);
}
```

### sc查找加载UserController的ClassLoader

`sc -d *UserController | grep classLoaderHash`{{execute T2}}

```bash
$ sc -d *UserController | grep classLoaderHash
classLoaderHash 1be6f5c3
```

可以发现是 spring boot `LaunchedURLClassLoader@1be6f5c3` 加载的。

请记下你的classLoaderHash,后面需要使用它。在这里,它是 `1be6f5c3`

注意:请使用你的classLoaderHash值覆盖 `<classLoaderHash>` ,然后手动执行下面所有所述命令:

### mc

保存好`/tmp/UserController.java`之后,使用`mc`(Memory Compiler)命令来编译,并且通过`-c`参数指定ClassLoader:

`mc -c <classLoaderHash> /tmp/UserController.java -d /tmp`

```bash
$ mc -c 1be6f5c3 /tmp/UserController.java -d /tmp
Memory compiler output:
/tmp/com/example/demo/arthas/user/UserController.class
Affect(row-cnt:1) cost in 346 ms
```

### redefine

再使用`redefine`命令重新加载新编译好的`UserController.class`

`redefine /tmp/com/example/demo/arthas/user/UserController.class`{{execute T2}}

```
$ redefine /tmp/com/example/demo/arthas/user/UserController.class
redefine success, size: 1
```

### 热修改代码结果

`redefine`成功之后,再次访问 https://[[HOST_SUBDOMAIN]]-80-[[KATACODA_HOST]].environments.katacoda.com/user/0 ,结果是:

```
{
"id": 0,
"name": "name0"
}
```
12 changes: 12 additions & 0 deletions tutorials/katacoda/command-mc-redefine-cn/finish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

在“mc-redefine”中,我们演示了了Arthas的mc-redefine命令。如果有更多的技巧或者使用疑问,欢迎在Issue里提出。

* Issues: https://github.com/alibaba/arthas/issues
* 文档: https://alibaba.github.io/arthas


如果您在使用Arthas,请让我们知道,您的使用对我们非常重要:[查看](https://github.com/alibaba/arthas/issues/111)

欢迎关注公众号,获取Arthas项目的信息,源码分析,案例实践。

![Arthas公众号](/arthas/scenarios/common-resources/assets/qrcode_gongzhonghao.jpg)
53 changes: 53 additions & 0 deletions tutorials/katacoda/command-mc-redefine-cn/index.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"title": "Arthas mc-redefine命令",
"description": "Arthas mc-redefine命令",
"difficulty": "精通者",
"time": "10-20 分钟",
"details": {
"steps": [
{
"title": "启动demo",
"text": "start-demo.md"
},
{
"title": "启动arthas-boot",
"text": "arthas-boot.md"
},
{
"title": "mc命令",
"text": "mc.md"
},
{
"title": "redefine命令",
"text": "redefine.md"
},
{
"title": "热更新代码",
"text": "case-jad-mc-redefine.md"
}
],
"intro": {
"text": "intro.md"
},
"finish": {
"text": "finish.md"
},
"assets": {
"host01": []
}
},
"environment": {
"uilayout": "terminal",
"showdashboard": true,
"dashboards": [
{
"name": "Web Port 80",
"port": 80
}
]
},
"backend": {
"imageid": "java",
"environmentsprotocol": "http"
}
}
23 changes: 23 additions & 0 deletions tutorials/katacoda/command-mc-redefine-cn/intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@



![Arthas](https://alibaba.github.io/arthas/_images/arthas.png)

`Arthas` 是Alibaba开源的Java诊断工具,深受开发者喜爱。在线排查问题,无需重启;动态跟踪Java代码;实时监控JVM状态。

`Arthas` 支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 `Tab` 自动补全功能,进一步方便进行问题的定位和诊断。

当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到JVM的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?

本教程会以一个普通的Spring Boot应用为例,演示mc-redefine命令。

* Github: https://github.com/alibaba/arthas
* 文档: https://alibaba.github.io/arthas/
7 changes: 7 additions & 0 deletions tutorials/katacoda/command-mc-redefine-cn/mc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

> Memory Compiler/内存编译器,编译`.java`文件生成`.class`

可以通过`-c`参数指定classloader,`-d`参数指定输出目录

编译生成`.class`文件之后,可以结合`redefine`命令实现热更新代码。
24 changes: 24 additions & 0 deletions tutorials/katacoda/command-mc-redefine-cn/redefine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

> 加载外部的`.class`文件,redefine jvm已加载的类。
参考:[Instrumentation#redefineClasses](https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html#redefineClasses-java.lang.instrument.ClassDefinition...-)

> 注意, redefine后的原来的类不能恢复,redefine有可能失败(比如增加了新的field),参考jdk本身的文档。
> `reset`命令对`redefine`的类无效。如果想重置,需要`redefine`原始的字节码。
> `redefine`命令和`jad`/`watch`/`trace`/`monitor`/`tt`等命令会冲突。执行完`redefine`之后,如果再执行上面提到的命令,则会把`redefine`的字节码重置。
> 原因是jdk本身redefine和Retransform是不同的机制,同时使用两种机制来更新字节码,只有最后修改的会生效。
### 参数说明

|参数名称|参数说明|
|---:|:---|
|[c:]|ClassLoader的hashcode|
|[p:]|外部的`.class`文件的完整路径,支持多个|


### redefine的限制

* 不允许新增加field/method
* 正在跑的函数,没有退出不能生效。
14 changes: 14 additions & 0 deletions tutorials/katacoda/command-mc-redefine-cn/start-demo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@




下载`demo-arthas-spring-boot.jar`,再用`java -jar`命令启动:

`wget https://github.com/hengyunabc/spring-boot-inside/raw/master/demo-arthas-spring-boot/demo-arthas-spring-boot.jar
java -jar demo-arthas-spring-boot.jar`{{execute T1}}

`demo-arthas-spring-boot`是一个很简单的spring boot应用,源代码:[查看](https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-arthas-spring-boot)

启动之后,可以访问80端口: https://[[HOST_SUBDOMAIN]]-80-[[KATACODA_HOST]].environments.katacoda.com

![Demo Web](/arthas/scenarios/common-resources/assets/demo-web.png)
16 changes: 16 additions & 0 deletions tutorials/katacoda/command-mc-redefine-en/arthas-boot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@




In the new `Terminal 2`, download `arthas-boot.jar` and start with the `java -jar` command:

`wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar`{{execute T2}}

`arthas-boot` is the launcher for `Arthas`. It lists all the Java processes, and the user can select the target process to be diagnosed.

Select the first process, type `1`{{execute T2}} ,then type `Enter`

After the Attach is successful, Arthas LOGO is printed. Enter `help`{{execute T2}} for more help.

![Arthas Boot](/arthas/scenarios/common-resources/assets/arthas-boot.png)
88 changes: 88 additions & 0 deletions tutorials/katacoda/command-mc-redefine-en/case-jad-mc-redefine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
This case introduces the ability to dynamically update code via the `jad`/`mc`/`redefine` command.

Currently, visiting http://localhost/user/0 will return a 500 error:

`curl http://localhost/user/0`{{execute T3}}

```
{"timestamp":1550223186170,"status":500,"error":"Internal Server Error","exception":"java.lang.IllegalArgumentException","message":"id < 1","path":"/user/0"}
```

This logic will be modified by `redefine` command below.

### Use jad command to decompile UserController

`jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java`{{execute T2}}

The result of jad command will be saved in the `/tmp/UserController.java` file.


Then open `Terminal 3`, use `vim` to edit `/tmp/UserController.java`:

`vim /tmp/UserController.java`{{execute T3}}

For example, when the user id is less than 1, it also returns normally without throwing an exception:

```java
@GetMapping(value={"/user/{id}"})
public User findUserById(@PathVariable Integer id) {
logger.info("id: {}", (Object)id);
if (id != null && id < 1) {
return new User(id, "name" + id);
// throw new IllegalArgumentException("id < 1");
}
return new User(id.intValue(), "name" + id);
}
```

### Use sc command to find the ClassLoader that loads the UserController

`sc -d *UserController | grep classLoaderHash`{{execute T2}}

```bash
$ sc -d *UserController | grep classLoaderHash
classLoaderHash 1be6f5c3
```

It can be found that it is loaded by spring boot `LaunchedURLClassLoader@1be6f5c3`.

Please write down your classLoaderHash here, in the case here, it's `1be6f5c3`. It will be used in the future steps.

Note: Please replace `<classLoaderHash>` with your classLoaderHash above, then execute the commands manually in the following steps:

### mc

After saving `/tmp/UserController.java`, compile with the `mc` (Memory Compiler) command and specify the ClassLoader with the `-c` option:

`mc -c <classLoaderHash> /tmp/UserController.java -d /tmp`

```bash
$ mc -c 1be6f5c3 /tmp/UserController.java -d /tmp
Memory compiler output:
/tmp/com/example/demo/arthas/user/UserController.class
Affect(row-cnt:1) cost in 346 ms
```

### redefine

Then reload the newly compiled `UserController.class` with the `redefine` command:

`redefine /tmp/com/example/demo/arthas/user/UserController.class`{{execute T2}}

```
$ redefine /tmp/com/example/demo/arthas/user/UserController.class
redefine success, size: 1
```

### Check the results of the hotswap code

After the `redefine` command is executed successfully, visit https://[[HOST_SUBDOMAIN]]-80-[[KATACODA_HOST]].environments.katacoda.com/user/0 again.

The result is:

```
{
"id": 0,
"name": "name0"
}
```
8 changes: 8 additions & 0 deletions tutorials/katacoda/command-mc-redefine-en/finish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

The `mc-redefine Tutorial` demonstrates the usage of mc-redefine. If you have more tips or questions, please feel free to ask in Issue.

* Issues: https://github.com/alibaba/arthas/issues
* Documentation: https://alibaba.github.io/arthas


If you are using Arthas, please let us know. Your use is very important to us: [View](https://github.com/alibaba/arthas/issues/111)
Loading

0 comments on commit 1172ab6

Please sign in to comment.