无需重启JVM动态增加日志实操 基于Arthas

无需重启JVM动态增加日志实操 基于Arthas

一、使用背景

通常,本地开发环境无法访问生产环境。如果在生产环境中遇到问题,则无法使用 IDE 远程调试。更糟糕的是,在生产环境中调试是不可接受的,因为它会暂停所有线程,导致服务暂停。

开发人员可以尝试在测试环境或者预发环境中复现生产环境中的问题。但是,某些问题无法在不同的环境中轻松复现,甚至在重新启动后就消失了。

如果您正在考虑在代码中添加一些日志以帮助解决问题,您将必须经历以下阶段:测试、预发,然后生产。这种方法效率低下,更糟糕的是,该问题可能无法解决,因为一旦 JVM 重新启动,它可能无法复现,如上文所述。

Arthas 旨在解决这些问题。开发人员可以在线解决生产问题。无需 JVM 重启,无需代码更改。 Arthas 作为观察者永远不会暂停正在运行的线程。

二、相关理论

对象是以类为模板创建的,每个类的普通非静态属性都是私有的,但是行为(方法)是共有的,类加载之后,类的行为存储在方法区。我们首先修改源码(比如增加日志),然后重新编译生成字节码文件,再让jvm重新加载该字节码,这样该类创建的所有对象的对应行为也就改变了,并且已经创建的对象本身的属性和状态不会改变。重新编译字节码很简单,我们主要需要考虑的是如何让jvm在不重启的前提下重新加载字节码文件。

  • 参考Java官方文档
    java.lang.instrument.Instrumentation
    看完文档之后,我们发现这么两个接口:redefineClasses 和 retransformClasses。一个是重新定义 class,一个是修改 class。
    都是替换已经存在的 class 文件,redefineClasses 是自己提供字节码文件替换掉已存在的 class 文件,retransformClasses 是在已存在的字节码文件上修改后再替换之。
    运行时直接替换类很不安全。比如把某个类的一个 field 给删除这种情况会引发异常。所以如文档中所言,instrument 存在诸多的限制,我们能做的基本上也就是简单修改方法内的一些行为,这对于打印一段日志来说完全足够。

  • 那怎么得到我们需要的 class 文件呢?
    一个最简单的方法,是把修改后的 Java文件重新编译一遍得到 class 文件,然后调用redefineClasses 替换。

  • 但是对于没有(或者拿不到,或者不方便修改)源码的文件我们应该怎么办呢?
    可以用来直接编辑字节码的框架,提供接口可以让我们方便地操作字节码文件,进行注入修改类的方法,动态创造一个新的类等等操作。其中最著名的框架应该就是 ASM 了,cglib、Spring 等框架中对于字节码的操作就建立在 ASM 之上。我们都知道,Spring 的 AOP 是基于动态代理实现的,Spring 会在运行时动态创建代理类,代理类中引用被代理类,在被代理的方法执行前后进行一些神秘的操作。

  • Spring 是怎么在运行时创建代理类的呢?动态代理的美妙之处,就在于我们不必手动为每个需要被代理的类写代理类代码,Spring 在运行时会根据需要动态地创造出一个类,这里创造的过程并非通过字符串写 Java 文件,然后编译成class 文件,然后加载。Spring 会直接“创造”一个 class 文件,然后加载,创造class 文件的工具,就是 ASM 了。到这里,我们知道了用 ASM 框架直接操作 class 文件,在类中加一段打印日志的代码,然后调用 retransformClasses 就可以了。

三、Arthas实操

参考Arthas官方文档 快速入门
教程
1. 启动测试程序math-game

curl -O https://arthas.aliyun.com/math-game.jar
java -jar math-game.jar

math-game是一个简单的程序,每隔一秒生成一个随机数,再执行质因数分解,并打印出分解结果。

2. 启动 arthas

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

选择应用 java 进程:

$ $ java -jar arthas-boot.jar
* [1]: 35542
  [2]: 71560 math-game.jar

math-game进程是第 2 个,则输入 2,再输入回车/enter。Arthas 会 attach 到目标进程上,并输出日志:
【这里在原来的终端中会开启一个Arthas的子终端窗口,在其中输入Arthas的特定命令进行操作】

[INFO] Try to attach process 71560
[INFO] Attach process 71560 success.
[INFO] arthas-client connect 127.0.0.1 3658
  ,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---.
 /  O  \ |  .--. ''--.  .--'|  '--'  | /  O  \ '   .-'
|  .-.  ||  '--'.'   |  |   |  .--.  ||  .-.  |`.  `-.
|  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-'    |
`--' `--'`--' '--'   `--'   `--'  `--'`--' `--'`-----'


wiki: https://arthas.aliyun.com/doc
version: 3.0.5.20181127201536
pid: 71560
time: 2018-11-28 19:16:24

$

3. 查看需增加日志类的源码并动态修改【由于正在运行的是class文件,需要进行反编译】
【在Arthas子窗口中执行下述命令】
jad反编译命令

$ jad demo.MathGame
# 默认情况下反编译结果里会带有ClassLoader信息,通过--source-only选项可以只打印源代码
$ jad --source-only demo.MathGame
# 以上两个命令只是查看反编译后的源码,需要讲反编译的源代码输出到Java文件中从而手写源代码增加日志(如果已有源代码可以用源代码增加日志然后编译)
# 使用jad反编译demo.MathGame输出到服务器上的指定文件夹中的文件:/Users/allen/Desktop/test/MathGame.java
$ jad --source-only demo.MathGame > /Users/allen/Desktop/test/MathGame.java

编译命令【编译之前记得修改源码,增加打印日志的语句】

# 编译MathGame.java文件到目录下/Users/allen/Desktop/test/
$ mc /Users/allen/Desktop/test/MathGame.java -d /Users/allen/Desktop/test/
# 或者也可以重新打开一个终端窗口进入目录/Users/allen/Desktop/test/,然后执行
$ javac MathGame.java

4. 热加载进JVM
【在Arthas子窗口中执行下述命令】

$ redefine /Users/allen/Desktop/test/MathGame.class
# 执行成功之后就可以看到新加的日志打印到终端了

5. 效果展示

正在运行的math-game.jar,未新增日志
请添加图片描述

执行动态加载新的class文件到JVM:
请添加图片描述

运行中增加日志打印System.out.println(“can run !!!”);效果:
增加日志后

参考文献

arthas官网
不重启JVM动态添加日志(阿里Arthas)

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
anylog 是一个可以在代码的任意区域无入侵地加入日志的工具,适用于线上问题排查。 anylog 为开发人员提供一个易于使用的平台,帮助开发人员在正在运行的系统中随时加入自己想要的日志,而免于修改代码和重启。 使用场景举例     1、一些同学在写代码时,把异常吃掉了,使得问题难以查找,可以使用这个工具,动态打印出被吃掉         的异常,而不用停机。     2、一些项目依赖第三方jar包,如果发生问题,但第三方包中无日志打印,以往可能需要重新编译第         三方包,加上日志重启服务,然后排查问题。但使用这个工具,就可以直接动态加入日志,而不用         修改第三方jar包,也不用重启。 已有功能     1、让系统打印某个exception的堆栈,无论此exception是否已经被吃掉都可打印     2、在某个指定类的某个方法的某一行,输出日志。     3、在某个指定类的某个方法的开始,输出日志。     4、在某个指定类的某个方法的结束,输出日志。       5、打印方法耗时,支持方法嵌套。     如果需要扩展新的功能(例如输出jvm的cpu占用,内存大小等),只需要实现spi中的     com.github.jobop.anylog.spi.TransformDescriptor      和com.github.jobop.anylog.spi.TransformHandler接口,     然后把实现的jar包放到providers目录中即可识别。 使用方法     1、获取运行程序:         1)可以到以下地址获取正式发行版:https://github.com/jobop/release/tree/master/anylog         2)你也可以clone下源码后,执行如下命令,生成运行程序,生成的运行程序将在dist目录下             生成windows版本:  mvn install             生成linux版本:  mvn install -Plinux     2、直接执行startup.bat或者startup.sh即可运行起来     3、访问 http://127.0.0.1:52808 即可使用 功能扩展     anylog利用spi机制实现其扩展,如果你想要对anylog增加新的功能(例如添加返回值打印的功能)可以按照如下步骤操作:     1、使用如下命令,生成一个spi实现工程,并导入eclipse     mvn archetype:generate -DarchetypeGroupId=com.github.jobop -DarchetypeArtifactId=anylogspi-archetype -DarchetypeVersion=1.0.4     2、参照该工程中已有的两个例子(一个是在方法开始插入日志,一个是在方法结束插入日志),实现TransformDescriptor和TransformHandler接口     3、把两个接口实现类的全路径,分别加到以下两个文件中         src/main/resources/META-INF/services/com.github.jobop.anylog.spi.TransformDescriptor         src/main/resources/META-INF/services/com.github.jobop.anylog.spi.TransformHandler     4、执行mvn install打包,在dist下会生成你的扩展实现jar。     5、把扩展实现jar拷贝到anylog的providers目录下,重启即可生效。     tips:在实现spi时,我们提供了SpiDesc注解,该注解作用在你实现的TransformDescriptor上,可以用来生成功能描述文字。          如果要深入了解spi机制,请自行google:java spi 标签:anylog
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值