>  기사  >  Java  >  Java 온라인 문제 해결을 위한 BTrace에 대한 자세한 설명

Java 온라인 문제 해결을 위한 BTrace에 대한 자세한 설명

黄舟
黄舟원래의
2017-08-23 11:33:522343검색

BTrace란 무엇입니까

BTrace는 온라인 문제를 확인하고 해결하는 킬러 도구입니다. BTrace는 스크립트를 작성하여 프로그램 실행 중에 모든 정보를 얻을 수 있으며, 서비스를 다시 시작할 필요가 없습니다. , 서비스를 다시 시작할 필요가 없습니다. 스크립트를 작성한 후 원본 프로그램 코드를 건드리지 않고 명령으로 직접 실행할 수 있습니다.

Principle

일반적으로 BTrace는 동적 바이트코드 수정 기술(Hotswap)을 기반으로 런타임 Java 프로그램의 추적 및 교체를 구현합니다. 일반적인 원리는 다음 공식으로 설명할 수 있습니다. 클라이언트(Java 컴파일 API + 첨부 API) + 에이전트(스크립트 구문 분석 엔진 + ASM + JDK6 계측) + 소켓Client(Java compile api + attach api) + Agent(脚本解析引擎 + ASM + JDK6 Instumentation) + Socket其实 BTrace 就是使用了 java attach api 附加 agent.jar ,然后使用脚本解析引擎+asm来重写指定类的字节码,再使用 instrument 实现对原有类的替换。

安装和配置

本次安装和配置在 Linux Ubuntu 14.04 下进行。目前 BTrace 的最新版本为 1.3.9,代码托管在 [github] 上。第一步,在github 上下载 releases 版 btrace-bin-1.3.9.tgz,zip 版的没有 build 目录。第二步,解压 btrace-bin-1.3.9.tgz 到一个目录即可,例如 /home/fengzheng/soft/btrace , 到这一步其实就可以用了,只是执行脚本的时候需要在 btrace 命令前加上绝对路径,如果想在任意目录可执行,进行下一步第三步,配置环境变量,配置的环境变量包括 JAVA_HOMEBTRACE_HOME ,例如我的配置如下:


export JAVA_HOME=/home/fengzheng/soft/jdk1.8.0_111
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib  
export PATH=${JAVA_HOME}/bin:$PATH
export BTRACE_HOME=/home/fengzheng/soft/btrace
export PATH=$PATH:$BTRACE_HOME/bin

之后执行命令 source /etc/profile ,使环境变量立即生效。接下来在任意目录执行 btrace命令,都可以执行成功了。

简单测试用例  

btrace 最简单的语法是 btrace $pid script.java,所以需要知道要探测的 Java程序的进程id,然后编写一个探测脚本即可。

1. 写一个常驻内存的 Java 程序,这里写了一个无限循环,每隔5秒钟输出一组计算结果,内容如下:


package kite.lab.utils;
/**
 * NumberUtil
 *
 * @author fengzheng
 * @date 2017/2/15
 */
public class NumberUtil {

    public int sum(){
        int result = 0;
        for(int i = 0; i< 100; i++){
            result += i * i;
        }
        return result;
    }

    public static void main(String[] args){
        while (true) {
            Thread.currentThread().setName("计算");
            NumberUtil util = new NumberUtil();
            int result = util.sum();
            System.out.println(result);
            try {
                Thread.sleep(5000);
            }catch (InterruptedException e){

            }
        }
    }
}

顺便说一下命令行编译和运行 Java 的过程:

编译javac -d . NumberUtil.java,定位到 NumberUtil.java 所在目录,然后执行此命令行,将会在当前目录(.表示当前目录)生成包名所示的目录结构,kite/lab/utils/NumberUtil.class

执行java kite.lab.utils.NumberUtil 即可  

2. 执行上面的程序后,可用 jps실제로 BTrace는 Java를 사용합니다. api를 첨부한 후 agent.jar을 첨부한 다음 스크립트 구문 분석 엔진 + asm을 사용하여 지정된 클래스의 바이트코드를 다시 작성한 다음 계측기를 사용하여 원래 클래스를 대체합니다.


설치 및 구성


이 설치 및 구성은 Linux Ubuntu 14.04에서 수행됩니다. BTrace의 최신 버전은 현재 1.3.9이며 코드는 [github]에서 호스팅됩니다.

첫 번째 단계는 github에서 릴리스 버전 btrace-bin-1.3.9.tgz를 다운로드하는 것입니다. zip 버전에는 빌드 디렉터리가 없습니다.

두 번째 단계는 /home/fengzheng/soft/btrace와 같은 디렉터리에 btrace-bin-1.3.9.tgz의 압축을 푸는 것입니다. 이 단계에서는 실제로 사용할 준비가 되었습니다. btrace 명령 앞에 절대 경로를 추가해야 합니다. 임의의 디렉터리에서 실행하려면 다음 단계로 진행하세요. 구성된 환경 변수에는 JAVA_HOMEBTRACE_HOME , 예를 들어 내 구성은 다음과 같습니다.

root@ubuntu:/home/fengzheng/codes/btrace# jps
10906 Jps
10860 NumberUtil
그런 다음 source /etc/profile를 사용하면 환경 변수가 즉시 적용됩니다. 그런 다음 임의의 디렉터리에서 <code>btrace 명령을 실행하면 성공적으로 실행됩니다.


간단한 테스트 사례

btrace 가장 간단한 구문은 btrace $pid script.java이므로 감지하려면 Java 프로그램의 프로세스 ID를 알아야 합니다. , 그런 다음 탐지 스크립트가 수행됩니다.

1. 메모리 상주 Java 프로그램을 작성합니다. 여기서는 5초마다 일련의 계산 결과를 출력하기 위해 무한 루프를 작성합니다.

🎜🎜🎜 🎜🎜
package kite;
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.Strings.strcat;
import static com.sun.btrace.BTraceUtils.jstack;
import static com.sun.btrace.BTraceUtils.println;
import static com.sun.btrace.BTraceUtils.str;

/**
 * NumberUtilBTrace
 *
 * @author fengzheng
 * @date 2017/6/20
 */
@BTrace
public class NumberUtilBTrace {

    @OnMethod(
            clazz="kite.lab.utils.NumberUtil",
            method="sum",
            location=@Location(Kind.RETURN)
    )
    public static void func(@Return int result) {
        println("trace: =======================");
        println(strcat("result:", str(result)));
        jstack();
    }
}
🎜🎜🎜 명령줄에서 Java를 컴파일하고 실행하는 프로세스: 🎜🎜🎜🎜🎜🎜🎜컴파일: javac -d NumberUtil.java code>에서 NumberUtil .java를 찾은 후 이 명령줄을 실행하면 패키지 이름에 표시된 디렉터리 구조가 현재 디렉터리(.는 현재 디렉터리를 나타냄), kite/lab/utils/NumberUtil.class🎜🎜🎜에 생성됩니다. 🎜🎜🎜🎜🎜🎜<strong>실행</strong>: <code>java kite.lab.utils.NumberUtil 후 🎜🎜🎜🎜 🎜🎜2. jps 명령을 사용하여 pid를 확인합니다. (일반적으로 프로그램을 시작하는 데 사용되는 계정은 루트 계정을 제외하고 jps를 실행하는 데 사용되는 계정에 따라 다릅니다.) jps 명령을 실행하면 다음 결과를 볼 수 있습니다. 🎜🎜🎜🎜
trace: =======================
result:328350
kite.lab.utils.NumberUtil.sum(NumberUtil.java:16)
kite.lab.utils.NumberUtil.main(NumberUtil.java:27)
🎜3 방금 실행한 Java 프로세스는 10860 🎜🎜4임을 알 수 있습니다. btrace 스크립트를 작성하면 다음과 같습니다. 🎜🎜🎜🎜
@OnMethod(clazz="/javax\.swing\..*/", method="/.*/")
public static void swingMethods( @ProbeClassName String probeClass, @ProbeMethodName String probeMethod) {
   print("entered " + probeClass + "."  + probeMethod);
}
🎜는 실행이 완료된 후를 의미합니다. (location=@Location(Kind.RETURN)은 실행 종료를 나타냄) 결과와 스택 정보가 출력됩니다 🎜🎜🎜🎜5 사전 컴파일: 사전 컴파일 명령을 사용하여 실행 전에 스크립트의 정확성을 확인할 수 있습니다. 명령은 javac와 유사한 명령인 btracec입니다. btracec NumberUtilBTrace.java🎜🎜🎜🎜🎜🎜6. 명령줄 실행을 호출합니다. btrace 10860 NumberUtilBTrace.java(로컬 파일에 저장하려는 경우 다음을 사용할 수 있습니다.) 리디렉션 명령 btrace 10860 NumberUtilBTrace.java > mylog.log) 인쇄된 정보는 다음과 같습니다. 🎜🎜사용 시나리오🎜

BTrace 是一个事后工具,所谓事后工具就是在服务已经上线了,但是发现存在以下问题的时候,可以用 BTrace。

  1. 比如哪些方法执行太慢,例如监控执行时间超过1s的方法

  2. 查看哪些方法调用了 System.gc() ,调用栈是怎样的

  3. 查看方法参数或对象属性

  4. 哪些方法发生了异常

多说一点,为了更好解决问题,最好还要配合事前准备和进行中监控,事前准备就是埋点嘛,在一些可能出现问题的方法中进行日志输出,进行中监控就是利用一些实时监控工具,例如 VisualVM 、jmc 这些带界面的工具或者 jdk 提供的命令行工具等,再高级一点的就是利用 Graphite 这样的Metrics 工具配合 web 界面展示出来。

使用限制

 

为了保证trace语句只读,最小化对被检测程序造成影响, BTrace对trace脚本有一些限制(比如不能改变被trace代码中的状态)

  • BTrace class不能新建类, 新建数组, 抛异常, 捕获异常,

  • 不能调用实例方法以及静态方法(com.sun.btrace.BTraceUtils除外)

  • 不能将目标程序和对象赋值给BTrace的实例和静态field

  • 不能定义外部, 内部, 匿名, 本地类

  • 不能有同步块和方法

  • 不能有循环

  • 不能实现接口, 不能扩展类

  • 不能使用assert语句, 不能使用class字面值

拦截方法定义

@OnMethod 可以指定 clazz 、method、location。由此组成了在什么时机(location 决定)监控某个类/某些类(clazz 决定)下的某个方法/某些方法(method 决定)。

如何定位

1. 精准定位

直接定位到一个类下的一个方法,上面测试用的例子就是

2. 正则表达式定位

正则表达式在两个"/" 之间,例如下面的例子,监控 javax.swing 包下的所有方法,注意正式环境中,范围尽可能小一点,太大了性能会有影响。

 


@OnMethod(clazz="/javax\\.swing\\..*/", method="/.*/")
public static void swingMethods( @ProbeClassName String probeClass, @ProbeMethodName String probeMethod) {
   print("entered " + probeClass + "."  + probeMethod);
}

通过在拦截函数的定义里注入@ProbeClassName String probeClass, @ProbeMethodName String probeMethod 参数,告诉脚本实际匹配到的类和方法名。

3. 按接口或继承类定位

例如要匹配继承或实现了 com.kite.base 的接口或基类的,只要在类前加上 + 号就可以了,例如

@OnMethod(clazz="+com.kite.base", method="doSome")

4. 按注解定位

在前面加上 @ 即可,例如@OnMethod(clazz="@javax.jws.WebService", method="@javax.jws.WebMethod")  

拦截时机

拦截时机由 location 决定,当然也可为同一个定位加入多个拦截时机,即可以在进入方法时拦截、方法返回时拦截、抛出异常时拦截

1. Kind.Entry与Kind.Return

分别表示函数的开始和返回,不写 location 的情况下,默认为 Kind.Entry,仅获取参数值,可以用 Kind.Entry ,要获取返回值或执行时间就要用 Kind.Return

2. Kind.Error, Kind.Throw和 Kind.Catch

表示异常被 throw 、异常被捕获还有异常发生但是没有被捕获的情况,在拦截函数的参数定义里注入一个Throwable的参数,代表异常



@OnMethod(clazz = "com.kite.demo", location = @Location(value = Kind.LINE, line = 20))
public static void onBind() {
   println("执行到第20行");
}
@OnMethod(clazz = "java.net.ServerSocket", method = "bind", location =@Location(Kind.ERROR)) 
public static void onBind(Throwable exception, @Duration long duration){ }

3. Kind.Call 和 Kind.Line  

Kind.Call 表示被监控的方法调用了哪些其他方法,例如:


@OnMethod(clazz = "com.kite",
            method = "login",
            location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/", where = Where.AFTER))
    public static void onBind(@Self Object self, @TargetInstance Object instance, @TargetMethodOrField String method, @Duration long duration){
        println(strcat("self: ", str(self)));
        println(strcat("instance: ", str(instance)));
        println(strcat("method: ", str(method)));
        println(strcat("duration(ms): ", str(duration / 1000000)));
    }

@Self 表示当前监控的函数所在类,如果是静态类则为空,@TargetInstance 表示函数中调用的方法或属性所在的类,如果是静态方法则为空,@TargetMethodOrField 表示调用的方法或属性,如果要获取执行时间,那么 where 必须设置为 Where.AFTER

Kind.Line 监测类是否执行到了设置的行数,例如:


@OnMethod(clazz = "com.kite.demo", location = @Location(value = Kind.LINE, line = 20))
public static void onBind() {

   println("执行到第20行");

}

几个例子

查看谁调用了GC


@OnMethod(clazz = "java.lang.System", method = "gc")
    public static void onSystemGC() {
        println("entered System.gc()");
        jstack();
    }

打印耗时超过100ms的方法


@OnMethod(clazz = "/com\\.kite\\.controller\\..*/",method = "/.*/",location = @Location(Kind.RETURN))
    public static void slowQuery(@ProbeClassName String pcn,@ProbeMethodName String probeMethod, @Duration long duration){
        if(duration > 1000000 * 100){
            println(strcat("类:", pcn));
            println(strcat("方法:", probeMethod));
            println(strcat("时长:", str(duration / 1000000)));
        }
    }

BTrace 提供了一系列的 sample, 可到 github 上查看。

注意问题

如果出现 Unable to open socket file: target process not responding or HotSpot VM not loaded 这个问题,可能的原因是执行 BTrace 脚本的用户和 Java 进程运行的用户不是同一个,使用 ps -aux | grep $pid查看一下 Java 进程的执行用户,保证和 BTrace 脚本执行用户相同即可 

위 내용은 Java 온라인 문제 해결을 위한 BTrace에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.