当前位置: 首页 > 工具软件 > frostmourne > 使用案例 >

frostmourne

彭骏
2023-12-01

1、快速入门

产品简介

中文名:霜之哀伤,意为神器、可切入的。

Frostmourne是一柄JVM分析利器,类似于Btrace,但无侵入且不需要编写脚本代码;提供本地和远程两种Attach方式;多平台支持;功能斑驳且易于扩展。

面向开发,线上环境可以用于处理故障和定位疑难杂症,线下可以用于分析项目的架构缺陷和方法的性能

使用场景

诊断内存泄露

常规的做法一般是先使用jmap命令生成jvm的heapdump,然后把heapdump文件导到本地用相关工具打开。有时候会因为本地内存不够打不开,也有时候因为要跟踪内存变化不得不多次heapdump,需要多次传输文件。

Frostmourne提供了分析这种问题的另一种思路,在运行时直接分析堆内存的结构。

内存泄漏的表现形式往往是某些对象创建后无法回收,因为有其他对象保存着它的引用,这类对象达到一定数量就会造成Outofmemory。

根据诊断的经验,保存这些对象引用的对象往往是同一个或同一类对象,比如list、map等集合类。 因此这个问题就转变为如何搞清楚那些保存其他对象引用数最多的对象是干嘛的。

Frostmourne提供了"top ref"命令用于查找堆内存中保存其他对象引用数最多的对象。接下来可以使用"ref"命令来查看是哪些对象引用了了该号对象,这样就可以找出问题出在哪一段逻辑里。

诊断方法性能

方法性能包含两个指标,响应时间&不合理的内部逻辑

平时你是怎么处理一个方法响应超时的现象?测试环境或许你会在需要监控的地方打桩,虽然你知道这样做不友好,但又苦于没有更好的方法;线上环境就更加无能为力了,只能依赖于日志和VM的性能数据,Frostmourne提供了“invoke -t”命令用于跟踪方法调用的详细信息,包括调用时间,入参出参等。

项目往往非一人开发完成,使用“invoke -t”去评测方法性能的时候发现start方法竟然调用同一个DAO次数在2次以上,那么肯定是service设计有问题

PS:双十一的时候广告的一个开发由于代码问题猜测HttpClient调用响应超时导致Kafka消息消费滞后,又苦于没有途径验证这种猜测,这也是推广这个产品的原因之一;商品压测发现取不到数据库连接,但是控制台发现连接都是空闲的,其实单纯看堆栈是很难发现问题,类似于这种问题可以结合“trace”命令和"find"、“ref”命令来定位方法调用和内存泄露的问题。

重现误吞的异常

开发经常会把一些重要的异常给吞掉,导致线上功能问题又苦于没有异常,使用"trace"命令可以为你还原所有的异常信息,线上这个特性其实很实用

数据订正

由于代码BUG或者脏数据经常会订正线上的数据,逻辑简单倒还好,直接写SQL也能解决问题,但是如果有复杂的业务逻辑,那势必要发布任务代码来订正数据,Frostmourne提供的"invoke"可以解决这一难题,当然横切方法调用这一功能不仅限于此,测试环境也可以免于测试代码的编写工序了不是?

字节码增强

也许一个开源软件一段代码需要被修改,但是开发环境配置很麻烦,你想很爽朗的实现这一需求么?

也许线上有一段BUG,但是已经来不及重新发布了,你究竟想做什么?

PS:Frostmourne的字节码增强采用了javassist,使用com.sun.corba.se.spi.presentation.rmi.StubAdapter来实现代理,对源字节码有侵入但无影响,即使Attach的代码已经被卸载了,源字节码没有被恢复也么の事情。

快速开始

本教程假定你拥有的权限 >= JVM USER,并且您本机和待操作JVM机器路由畅通

Step1:下载代码

下载Frostmourne代码,并且mv clean install

cd Frostmourne

> mvn clean install

cd deploy/target/dev/bin/

 

Step2:启动客户端

客户端启动有两种途径,本地模式和远程模式,请先确保您本地的$JAVA_HOME配置正确,JVM部署在Windows的机器还需要确保$JAVA_HOME/jre/bin位于$Path中

本地启动:

> sh fm.sh

远程启动:

> sh fm.sh 127.0.0.1 karry/123456

Step3:Attach到JVM

主要是将Agent包Attach到指定的JVM上

> frostmourne>0. 1935 org.apache.zookeeper.server.quorum.QuorumPeerMain /Users/apple/software/zookeeper-3.4.6/bin/../conf/zoo.cfg

  frostmourne>1. 421

> frostmourne>input [0-1] to choose vm!

> 0

> frostmourne>connected!

>

Step4:执行命令

下文命令有详细的介绍,这里就不罗列了

> frostmourne>connected!

find service

> frostmourne>60   [class]      com.suning.tools.frostmourne.agent.service.TimingService$1                         [AgentClassLoader]

> frostmourne>find 61 classes like 'service', please choose one typing like 'find 0'!

find 60

> frostmourne>type        obj-id  detail

> frostmourne>----------------------------

> frostmourne>[instance]  @0      <>

> frostmourne>----------------------------

> frostmourne>find 1 instances of com.suning.tools.frostmourne.agent.service.TimingService$1

>

Step5:关闭客户端

ctrl+c or use close command

> frostmourne>find 1 instances of com.suning.tools.frostmourne.agent.service.TimingService$1

clear

> close

系统支持

JVM平台:JVM语言,支持MACOS、Linux64、Windows64

客户端:安装java,支持(linux、Macos、windows)

2、原理&设计

原作者:陈**(https://github.com/chenjw/knife)

现代码库:http://10.27.35.29/svn/fm/trunk

修正点:

1、修复find命令导致class永久增长的BUG

2、ant工程重构、工程瘦身

3、修复nativehelper64.so在公司测试环境加载失败的BUG

待修正:

1、AgentMain启动类无法卸载的问题

2、源字节码持久化策略,风险控制

3、监控Native Memory内存变化

4、检测已加载资源的释放情况,包括Object和Class

5、Trace优化,方式栈溢出

相关技术

instrument

http://docs.oracle.com/javase/6/docs/technotes/guides/instrumentation/index.html

http://jiangbo.me/blog/2012/02/21/java-lang-instrument/

jni

http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html

jvmti

http://docs.oracle.com/javase/6/docs/platform/jvmti/jvmti.html

javassist

http://www.jboss.org/javassist/

jline

http://jline.sourceforge.net/

系统架构

How to attach

System Architecture

               

功能扩展

拓展点

Frostmourne是基于JVMTI、SHELL、Instrument来实现的JVM数据分析,扩展的方向分为横向拓展和纵向拓展。

横向拓展:

1、JVMTI提供的其他函数暂未拿来分析,比如线程、堆栈、调试、事件等(功能太BT以至于用不太到),详见:https://docs.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#FunctionSection

2、SHELL目前提供了Top分析线程,虽然通过直接执行top命令也可以找到目标线程号,但是并不能得到线程的详细数据,可以结合JVMTI继续分析线程的状态和当前栈详细

纵向拓展:

1、依据现有JVMTI封装的native接口实现新的command或依赖现有的command实现新的参数,比如说spring命令

2、外层封装实现权限控制和web console

拓展口

具体拓展还需要阅读相关代码,这里只能给你提供入口来加快您的理解

1、service端

Command Interface

1

2

3

4

5

6

7

8

9

10

11

12

13

14

/**

 * 每个CommandHandler实现对应一个方法(比如"find","cd"等)的处理逻辑

 *

 * @author karry

 *

 */

public interface CommandHandler {

    public void declareArgs(ArgDef argDef);

    public void handle(Args args, CommandDispatcher dispatcher)

            throws Exception;

}

Command Demo:

public class CdCommandHandler implements CommandHandler {

    public void handle(Args args, CommandDispatcher dispatcher) {

        String param = args.arg("object-id");

        Object obj = null;

        if ("..".equals(param)) {

            obj = ServiceRegistry.getService(HistoryService.class).pre();

        else {

            int index = Integer.parseInt(param);

            obj = ServiceRegistry.getService(HistoryService.class).cd(index);

        }

        Agent.sendResult(ResultHelper.newResult("into "

                + ServiceRegistry.getService(ObjectHolderService.class).toId(

                        obj) + ToStringHelper.toString(obj)));

    }

    public void declareArgs(ArgDef argDef) {

        argDef.setDefinition("cd <object-id>");

    }

}

最后需要在META-INF/services的com.suning.tools.frostmourne.agent.core.CommandHandler文件中加入你实现的命令

2、client端

Formater Interface

public interface TypePrintFormater<T> {

    public Class<T> getType();

    public void printObject(T obj);

}

Formater Demo

public class ClassListFormater extends BasePrintFormater<ClassListInfo> {

    @Override

    protected void print(ClassListInfo classListInfo) {

        PreparedTableFormater table = new PreparedTableFormater(printer, grep);

        table.setTitle("idx""type""name""classloader");

        ClassInfo[] classInfos = classListInfo.getClasses();

        int i = 0;

        List<String> classNames = new ArrayList<String>();

        if (classInfos != null) {

            for (ClassInfo info : classInfos) {

                classNames.add(info.getName());

                table.addLine(

                        String.valueOf(i),

                        info.isInterface() ? "[interface]" "[class]",

                        info.getName(),

                        "["

                                + StringHelper.substringAfterLast(

                                        info.getClassLoader(), ".") + "]");

                i++;

            }

        }

        table.print();

        this.completeHandler.setArgCompletors(classNames

                .toArray(new String[classNames.size()]));

        this.printLine("find " + i + " classes like '"

                + classListInfo.getExpression()

                "', please choose one typing like 'find 0'!");

    }

}

最后需要在META-INF/services的com.suning.tools.frostmourne.client.formater.TypePrintFormater加入你实现的Formatter

开发部署

构建开发环境

1、安装maven、java,配置环境变量

2、在你的workspace目录中执行"svn co http://10.27.35.29/svn/fm/branches/fm_V1.0.0 Frostmourne/"

3、Native代码需要你到相应的目录下执行"make clean","make all"生成动态链接库,并拷贝到“service/src/main/resources“目录下

4、配置你本地的“antx-dev.properties”,开始编译“mvn clean install”

5、最终的可执行脚本位于"deploy"目录下,或许你的$JAVA_HOME/lib/tools.jar并不存在,那你需要找到它,并拷贝到"deploy/target/dev/lib_ext"目录下

工程目录结构

1、frostmourne.agent : agent包的源码工程,负责agent的安装和初始化

2、frostmourne.client : client包的源码工程,和连接console和server的桥梁

3、frostmourne.server : server包的源码工程,主要的功能实现

4、frostmourne.shared : 二方库工程,被client和server所依赖

5、frostmourne.native : 项目依赖的C源码工程,同一份源码,linux和windows平台不同的编译脚本

6、frostmourne.deploy : 部署工程,负责依赖的组装和安装包的生成

依赖的三方库

1、client端依赖的第三方包有:jline-1.0.jar,fastjson-1.1.17.jar,tools.jar

2、server端依赖的第三方包(会使用独立的classloader加载到目标jvm中)有:fastjson-1.1.17.jar,misc.javassist-3.9.0.GA.jar,tools.jar

其中tools.jar都会从当前机器的jdk安装目录中加载。运行时需要设置当前机器的环境变量"JAVA_HOME"到jdk根目录。

TO LIST

1、源字节码持久化

2、Spring功能优化

3、Agent Class的卸载

4、自定义字节码增强

5、权限控制

6、Trace优化,防止栈溢出

3、命令详解

help

特定命令如何使用?

有哪些命令?

cd

设置某个对象为“目标对象”. 类似'invoke' 和 'ls' 这些命令都是针对目标“对象操作”的,任何时候当输出中包含 '@15' 这样的标记时 (比如 'find' 命令的结果, 或 'invoke' 命令的返回值和参数), 这个对象都可以通过 'cd 15' 的方式标记为“目标对象”。

clear

清理掉所有设置的上下文信息,包括恢复字节码的增强

close

关闭client连接,你也可以使用^C

find

find命令用于寻找堆内存中的某个对象,主要思路为遍历堆内存,根据类名查找某个类的所有实例,然后再选择实际需要的某个实例,支持模糊查询。

gc

System.gc()而已

invoke

这个命令用来调用某个方法,并且跟踪这个方法调用内部的执行情况。

如果设置了 '-t' 则会跟踪所有方法内部的调用。比如方法 'a' 中如果调用了方法 'b',则在 'invoke a' 时,也会递归输出 'b' 中的执行情况。

设置 '-f <filter-expretion> '可以用来过滤掉方法调用输出中不需要显示的内容。

设置‘-c'可以用来统计每个方法的执行次数

设置‘-sb’可以用来设置需要被过滤的Spring Bean的ID,以‘,’分割

invoke-expression 为FastJson的Json串,也可以用‘@+数字’来描述一个对象

ls

查看字段:

查看方法:

查看构造函数:

查看集合:

new

创建的对象仅被Agent的ObjectHolder所引用,如果没有新的引用,将在Agent卸载后被GC回收

ref

可以查看自己引用的对象和引用自身的对象

set

修改属性值。可以输入'类名.属性名' 来表示设置静态变量值。或直接输入 '属性名' 来表示 设置“目标对象”的静态或非静态方法。

一个表达式表示要设置的值,如果为json格式,则会根据目标属性的类型做自动转换。 如果为 '@' 加数字的类型,则表示直接设置为该对象id对应的对象。

‘-s’表示显式设置静态属性,用来避免误解

top

top ref 命令往往和ref命令一起使用,找出泄露点

top thread后续考虑结合JVMTI的堆栈分析功能来实现

trace

和invoke不同的时,这个命令用来等待某个符合条件的方法调用(如通过类名和方法名匹配),当符合条件的调用发生时会输出方法调用的过程、入参、返回值、响应时间等参数。

‘-n’表示跟踪的次数

view

'-c'查看加载的 classLoader

'-s’查看加载的类名,一直致力于寻找加载的文件名,尝试了多种方式都不尽如人意。

spring

与parent context共用一个classloader,仅仅提供了spring配置文件的上传,或许你能用来起定时任务啊~代码还没有可以上传,须待开发

btrace

埋点的一个功能,类似于btrace,打印出当前方法调用的调用栈、线程标识、关键字信息

'-p'是否持久化增强字节码

'-k'自定义的关键字

效果:

5、Download

released:fm-dev.zip

released1.1[支持btrace]:fm-dev.zip

 类似资料:

相关阅读

相关文章

相关问答