性能工具之Java调试工具BTrace入门

Posted ZeeBJ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了性能工具之Java调试工具BTrace入门相关的知识,希望对你有一定的参考价值。

引言

在我们对Java应用做问题分析的时候,往往采用log进行问题定位和分析,但是如果我们的log缺乏相关的信息呢?远程调试会影响应用的正常工作,修改代码重新部署应用,实时性和灵活性难以保证,有没有不影响正常应用运行,又灵活并无侵入性的方法呢?

答案是有,它就是Java中的神器-BTrace
BTrace是什么?

BTrace使用Java的Attach技术,可以让我们无缝的将我们BTrace脚本挂到JVM上,通过脚本你可以获取到任何你想拿到的数据,在侵入性和安全性都非常可靠,特别是定位线上问题的神器。

BTrace原理

BTrace是基于动态字节码修改技术(Hotswap)向目标程序的字节码注入追踪代码。

安装配置

关于BTrace的安装配置使用,此处就不再重复造轮子,网上有太多的教程。

官网地址:https://github.com/btraceio/b...
注意事项

生产环境可以使用,但修改的字节码不会被还原,使用Btrace时,需要确保追踪的动作是只读的(即:追踪行为不能修改目标程序的状态)和有限的行为(即:追踪行为需要在有限的时间内终止),一个追踪行为需要满足以下的限制:

不能创建新的对象
不能创建新的数组
不能抛出异常
不能捕获异常
不能对实例或静态方法调用-只有从BTraceUtils中的public static方法中或在当前脚本中声明的方法,可以被BTrace调用
不能有外部,内部,嵌套或本地类
不能有同步块或同步方法
不能有循环(for,while,do..while)
不能继承抽象类(父类必须是java.lang.Object)
不能实现接口
不能有断言语句
不能有class保留字
以上的限制可以通过通过unsafe模式绕过。追踪脚本和引擎都必须设置为unsafe模式。脚本需要使用注解为 @BTrace(unsafe=true),需要修改BTrace安装目录下bin中btrace脚本将 -Dcom.sun.btrace.unsafe=false改为 -Dcom.sun.btrace.unsafe=true。

注:关于unsafe的使用,如果你的程序一旦被btrace追踪过,那么unsafe的设置会一直伴随该进程的整个生命周期。如果你修改了unsafe的设置,只有通过重启目标进程,才能获得想要的结果。所以该用法不是很好使用,如果你的应用不能随便重启,那么你在第一次使用btrace最终目标进程之前,先想好到底使用那种模式来启动引擎。
使用示例

拦截一个普通方法

control方法

@GetMapping
(
value
=

"/arg1"
)

public

String
arg1
(
@RequestParam
(
"name"
)

String
name
)

throws

InterruptedException

{

Thread
.
sleep
(
2000
);

return

"7DGroup,"

+
name
;

}
BTrace脚本

/**

  • 拦截示例
    */

@BTrace
public

class

PrintArgSimple

{

@OnMethod
(

//类名

        clazz 

=

"com.techstar.monitordemo.controller.UserController"
,

//方法名

        method 

=

"arg1"
,

//拦截时刻:入口

        location 

=

@Location
(
Kind
.
ENTRY
))

/**

 * 拦截类名和方法名
 */

public

static

void
anyRead
(
@ProbeClassName

String
pcn
,

@ProbeMethodName

String
pmn
,

AnyType
[]
args
)

{

BTraceUtils
.
printArray
(
args
);

BTraceUtils
.
println
(
pcn
+

","

+
pmn
);

BTraceUtils
.
println
();

}
}
拦截结果:

192
:
Btrace
apple$ jps
-
l
369

5889

/
Users
/
apple
/
Downloads
/
performance
/
apache
-
jmeter
-
4.0
/
bin
/
ApacheJMeter
.
jar
25922
sun
.
tools
.
jps
.
Jps
23011
org
.
jetbrains
.
idea
.
maven
.
server
.
RemoteMavenServer
25914
org
.
jetbrains
.
jps
.
cmdline
.
Launcher
25915
com
.
techstar
.
monitordemo
.
MonitordemoApplication
192
:
Btrace
apple$ btrace
25915

PrintArgSimple
.
java
[
zuozewei
,

]
com
.
techstar
.
monitordemo
.
controller
.
UserController
,
arg1
[
zee
,

]
com
.
techstar
.
monitordemo
.
controller
.
UserController
,
arg1
拦截构造函数

构造函数

@Data
public

class

User

{

private

int
id
;

private

String
name
;
}
control方法

@GetMapping
(
value
=

"/arg2"
)

public

User
arg2
(
User
user
)

{

return
user
;

}
BTrace脚本

/**

  • 拦截构造函数
    */

@BTrace
public

class

PrintConstructor

{

@OnMethod
(
clazz
=

"com.techstar.monitordemo.domain.User"
,
method
=

"<init>"
)

public

static

void
anyRead
(
@ProbeClassName

String
pcn
,

@ProbeMethodName

String
pmn
,

AnyType
[]
args
)

{

BTraceUtils
.
println
(
pcn
+

","

+
pmn
);

BTraceUtils
.
printArray
(
args
);

BTraceUtils
.
println
();

}
}
拦截结果

192
:
Btrace
apple$ btrace
34119

PrintConstructor
.
java
com
.
techstar
.
monitordemo
.
domain
.
User
,<
init

[
1
,
zuozewei
,

]
拦截同名函数,以参数区分

control方法

@GetMapping
(
value
=

"/same1"
)

public

String
same
(
@RequestParam
(
"name"
)

String
name
)

{

return

"7DGroup,"

+
name
;

}

@GetMapping
(
value
=

"/same2"
)

public

String
same
(
@RequestParam
(
"id"
)

int
id
,

@RequestParam
(
"name"
)

String
name
)

{

return

"7DGroup,"

+
name
+

","

+
id
;

}
BTrace脚本

/**

  • 拦截同名函数,通过输入的参数区分
    */

@BTrace
public

class

PrintSame

{

@OnMethod
(
clazz
=

"com.techstar.monitordemo.controller.UserController"
,
method
=

"same"
)

public

static

void
anyRead
(
@ProbeClassName

String
pcn
,

@ProbeMethodName

String
pmn
,

String
name
)

{

BTraceUtils
.
println
(
pcn
+

","

+
pmn
+

","

+
name
);

BTraceUtils
.
println
();

}
}
拦截结果

192
:
Btrace
apple$ jps
-
l
369

5889

/
Users
/
apple
/
Downloads
/
performance
/
apache
-
jmeter
-
4.0
/
bin
/
ApacheJMeter
.
jar
34281
sun
.
tools
.
jps
.
Jps
34220
org
.
jetbrains
.
jps
.
cmdline
.
Launcher
34221
com
.
techstar
.
monitordemo
.
MonitordemoApplication
192
:
Btrace
apple$ btrace
34221

PrintSame
.
java
com
.
techstar
.
monitordemo
.
controller
.
UserController
,
same
,
zuozewei
com
.
techstar
.
monitordemo
.
controller
.
UserController
,
same
,
zuozewei
com
.
techstar
.
monitordemo
.
controller
.
UserController
,
same
,
zuozewei
拦截方法返回值

BTrace脚本

/**

  • 拦截返回值
    */

@BTrace
public

class

PrintReturn

{

@OnMethod
(
clazz
=

"com.techstar.monitordemo.controller.UserController"
,
method
=

"arg1"
,

//拦截时刻:返回值

        location 

=

@Location
(
Kind
.
RETURN
))

public

static

void
anyRead
(
@ProbeClassName

String
pcn
,

@ProbeMethodName

String
pmn
,

@Return

AnyType
result
)

{

BTraceUtils
.
println
(
pcn
+

","

+
pmn
+

","

+
result
);

BTraceUtils
.
println
();

}
}
拦截结果

192
:
Btrace
apple$ jps
-
l
34528
org
.
jetbrains
.
jps
.
cmdline
.
Launcher
34529
com
.
techstar
.
monitordemo
.
MonitordemoApplication
369

5889

/
Users
/
apple
/
Downloads
/
performance
/
apache
-
jmeter
-
4.0
/
bin
/
ApacheJMeter
.
jar
34533
sun
.
tools
.
jps
.
Jps
192
:
Btrace
apple$ btrace
34529

PrintReturn
.
java
com
.
techstar
.
monitordemo
.
controller
.
UserController
,
arg1
,
7DGroup
,
zuozewei
异常分析

有时候开发人员对异常处理不合理,导致某些重要异常人为被吃掉,并且没有日志或者日志不详细,导致性能分析定位问题困难,我们可以使用BTrace来处理
control方法

@GetMapping
(
value
=

"/exception"
)

public

String
exception
()

{

try

{

System
.
out
.
println
(
"start..."
);

System
.
out
.
println
(
1

/

0
);

//模拟异常

System
.
out
.
println
(
"end..."
);

}

catch

(
Exception
e
)

{}

return

"successful..."
;

}
BTrace脚本

/**

  • 有时候,有些异常被人为吃掉,日志又没有打印,这个时候可以用该类定位问题
  • This example demonstrates printing stack trace
  • of an exception and thread local variables. This
  • trace script prints exception stack trace whenever
  • java.lang.Throwable\'s constructor returns. This way
  • you can trace all exceptions that may be caught and
  • "eaten" silently by the traced program. Note that the
  • assumption is that the exceptions are thrown soon after
  • creation [like in "throw new FooException();"] rather
  • that be stored and thrown later.
    */

@BTrace
public

class

PrintOnThrow

{

// store current exception in a thread local

// variable (@TLS annotation). Note that we can\'t

// store it in a global variable!

@TLS

static

Throwable
currentException
;

// introduce probe into every constructor of java.lang.Throwable

// class and store "this" in the thread local variable.

@OnMethod
(
clazz
=

"java.lang.Throwable"
,
method
=

"<init>"
)

public

static

void
onthrow
(
@Self

Throwable
self
)

{

    currentException 

=
self
;

}

@OnMethod
(
clazz
=

"java.lang.Throwable"
,
method
=

"<init>"
)

public

static

void
onthrow1
(
@Self

Throwable
self
,

String
s
)

{

    currentException 

=
self
;

}

@OnMethod
(
clazz
=

"java.lang.Throwable"
,
method
=

"<init>"
)

public

static

void
onthrow1
(
@Self

Throwable
self
,

String
s
,

Throwable
cause
)

{

    currentException 

=
self
;

}

@OnMethod
(
clazz
=

"java.lang.Throwable"
,
method
=

"<init>"
)

public

static

void
onthrow2
(
@Self

Throwable
self
,

Throwable
cause
)

{

    currentException 

=
self
;

}

// when any constructor of java.lang.Throwable returns

// print the currentException\'s stack trace.

@OnMethod
(
clazz
=

"java.lang.Throwable"
,
method
=

"<init>"
,
location
=

@Location
(
Kind
.
RETURN
))

public

static

void
onthrowreturn
()

{

if

(
currentException
!=

null
)

{

Threads
.
jstack
(
currentException
);

BTraceUtils
.
println
(
"====================="
);

        currentException 

=

null
;

}

}
}
拦截结果

192
:
Btrace
apple$ jps
-
l
369

5889

/
Users
/
apple
/
Downloads
/
performance
/
apache
-
jmeter
-
4.0
/
bin
/
ApacheJMeter
.
jar
34727
sun
.
tools
.
jps
.
Jps
34666
org
.
jetbrains
.
jps
.
cmdline
.
Launcher
34667
com
.
techstar
.
monitordemo
.
MonitordemoApplication
192
:
Btrace
apple$ btrace
34667

PrintOnThrow
.
java
java
.
lang
.
ClassNotFoundException
:
org
.
apache
.
catalina
.
webresources
.
WarResourceSet

java

.
net
.
URLClassLoader
.
findClass
(
URLClassLoader
.
java
:
381
)

java

.
lang
.
ClassLoader
.
loadClass
(
ClassLoader
.
java
:
424
)

java

.
lang
.
ClassLoader
.
loadClass
(
ClassLoader
.
java
:
411
)

sun

.
misc
.
Launcher$AppClassLoader
.
loadClass
(
Launcher
.
java
:
349
)

java

.
lang
.
ClassLoader
.
loadClass
(
ClassLoader
.
java
:
357
)

org

.
apache
.
catalina
.
webresources
.
StandardRoot
.
isPackedWarFile
(
StandardRoot
.
java
:
656
)

org

.
apache
.
catalina
.
webresources
.
CachedResource
.
validateResource
(
CachedResource
.
java
:
109
)

org

.
apache
.
catalina
.
webresources
.
Cache
.
getResource
(
Cache
.
java
:
69
)

org

.
apache
.
catalina
.
webresources
.
StandardRoot
.
getResource
(
StandardRoot
.
java
:
216
)

org

.
apache
.
catalina
.
webresources
.
StandardRoot
.
getResource
(
StandardRoot
.
java
:
206
)

org

.
apache
.
catalina
.
mapper
.
Mapper
.
internalMapWrapper
(
Mapper
.
java
:
1027
)

org

.
apache
.
catalina
.
mapper
.
Mapper
.
internalMap
(
Mapper
.
java
:
842
)

org

.
apache
.
catalina
.
mapper
.
Mapper
.
map
(
Mapper
.
java
:
698
)

org

.
apache
.
catalina
.
connector
.
CoyoteAdapter
.
postParseRequest
(
CoyoteAdapter
.
java
:
679
)

org

.
apache
.
catalina
.
connector
.
CoyoteAdapter
.
service
(
CoyoteAdapter
.
java
:
336
)

org

.
apache
.
coyote
.
http11
.
Http11Processor
.
service
(
Http11Processor
.
java
:
800
)

org

.
apache
.
coyote
.
AbstractProcessorLight
.
process
(
AbstractProcessorLight
.
java
:
66
)

org

.
apache
.
coyote
.
AbstractProtocol$ConnectionHandler
.
process
(
AbstractProtocol
.
java
:
800
)

org

.
apache
.
tomcat
.
util
.
net
.
NioEndpoint$SocketProcessor
.
doRun
(
NioEndpoint
.
java
:
1471
)

org

.
apache
.
tomcat
.
util
.
net
.
SocketProcessorBase
.
run
(
SocketProcessorBase
.
java
:
49
)

java

.
util
.
concurrent
.
ThreadPoolExecutor
.
runWorker
(
ThreadPoolExecutor
.
java
:
1149
)

java

.
util
.
concurrent
.
ThreadPoolExecutor$Worker
.
run
(
ThreadPoolExecutor
.
java
:
624
)

org

.
apache
.
tomcat
.
util
.
threads
.
TaskThread$WrappingRunnable
.
run
(
TaskThread
.
java
:
61
)

java

.
lang
.
Thread
.
run
(
Thread
.
java
:
748

)

...
定位某个超过阈值的函数

BTrace脚本

**

*

探测某个包路径下的方法执行时间是否超过某个阈值的程序,如果超过了该阀值,则打印当前线程的栈信息。

*/
import
com
.
sun
.
btrace
.
BTraceUtils
;
import
com
.
sun
.
btrace
.
annotations
.*;
import

static
com
.
sun
.
btrace
.
BTraceUtils
.*;
@BTrace
public

class

PrintDurationTracer

{

@OnMethod
(
clazz
=

"/com\\.techstar\\.monitordemo\\..*/"
,
method
=

"/.*/"
,
location
=

@Location
(
Kind
.
RETURN
))

public

static

void
trace
(
@ProbeClassName

String
pcn
,

@ProbeMethodName

String
pmn
,

@Duration

long
duration
)

{

//duration的单位是纳秒

if

(
duration

1000

*

1000

*

2
)

{

BTraceUtils
.
println
(
Strings
.
strcat
(
Strings
.
strcat
(
pcn
,

"."
),
pmn
));

BTraceUtils
.
print
(
" 耗时:"
);

BTraceUtils
.
print
(
duration
);

BTraceUtils
.
println
(
"纳秒,堆栈信息如下"
);

        jstack

();

}

}
}
拦截结果

192
:
Btrace
apple$ btrace
39644

PrintDurationTracer
.
java
com
.
techstar
.
monitordemo
.
controller
.
Adder
.
execute
耗时:
1715294657
纳秒,堆栈信息如下
com
.
techstar
.
monitordemo
.
controller
.
Adder
.
execute
(
Adder
.
java
:
13
)
com
.
techstar
.
monitordemo
.
controller
.
Main
.
main
(
Main
.
java
:
10
)
com
.
techstar
.
monitordemo
.
controller
.
Adder
.
execute
耗时:
893795666
纳秒,堆栈信息如下
com
.
techstar
.
monitordemo
.
controller
.
Adder
.
execute
(
Adder
.
java
:
13
)
com
.
techstar
.
monitordemo
.
controller
.
Main
.
main
(
Main
.
java
:
10
)
com
.
techstar
.
monitordemo
.
controller
.
Adder
.
execute
耗时:
1331363658
纳秒,堆栈信息如下
com
.
techstar
.
monitordemo
.
controller
.
Adder
.
execute
(
Adder
.
java
:
13
)
追踪方法执行时间

BTrace脚本

/**

  • 追踪某个方法的执行时间,实现原理同AOP一样。
    */

@BTrace
public

class

PrintExecuteTimeTracer

{

@TLS

static

long
beginTime
;

@OnMethod
(
clazz
=

"com.techstar.monitordemo.controller.Adder"
,
method
=

"execute"
)

public

static

void
traceExecuteBegin
()

{

    beginTime 

=
timeMillis
();

}

@OnMethod
(
clazz
=

"com.techstar.monitordemo.controller.Adder"
,
method
=

"execute"
,
location
=

@Location
(
Kind
.
RETURN
))

public

static

void
traceExecute
(
int
arg1
,

int
arg2
,

@Return

int
result
)

{

BTraceUtils
.
println
(
strcat
(
strcat
(
"Adder.execute 耗时:"
,
str
(
timeMillis
()

-
beginTime
)),

"ms"
));

BTraceUtils
.
println
(
strcat
(
"返回结果为:"
,
str
(
result
)));

}
}
拦截结果

192
:
Btrace
apple$ btrace
40863

PrintExecuteTimeTracer
.
java
Adder
.
execute
耗时:
803ms
返回结果为:
797
Adder
.
execute
耗时:
1266ms
返回结果为:
1261
Adder
.
execute
耗时:
788ms
返回结果为:
784
Adder
.
execute
耗时:
1524ms
返回结果为:
1521
Adder
.
execute
耗时:
1775ms
性能分析

压测的时候经常发现某一个服务变慢了,但是由于这个服务有很多的业务逻辑和方法构成,这个时候就不好定位到底慢在哪个地方。BTrace可以解决这个问题,只需要大概定位问题可能存在的地方,通过包路径模糊匹配,就可以找到问题。

BTrace脚本

/**
*

  • Description:
  • This script demonstrates new capabilities built into BTrace 1.2
  • Shortened syntax - when omitting "public" identifier in the class
  • definition one can safely omit all other modifiers when declaring methods
  • and variables
  • Extended syntax for @ProbeMethodName annotation - you can use
  • parameter to request a fully qualified method name instead of
  • the short one
  • Profiling support - you can use {@linkplain Profiler} instance to gather
  • performance data with the smallest overhead possible
    */

@BTrace
class

Profiling

{

@Property

Profiler
profiler
=

BTraceUtils
.
Profiling
.
newProfiler
();

@OnMethod
(
clazz
=

"/com\\.techstar\\..*/"
,
method
=

"/.*/"
)

void
entry
(
@ProbeMethodName
(
fqn
=

true
)

String
probeMethod
)

{

BTraceUtils
.
Profiling
.
recordEntry
(
profiler
,
probeMethod
);

}

@OnMethod
(
clazz
=

"/com\\.techstar\\..*/"
,
method
=

"/.*/"
,
location
=

@Location
(
value
=

Kind
.
RETURN
))

void
exit
(
@ProbeMethodName
(
fqn
=

true
)

String
probeMethod
,

@Duration

long
duration
)

{

BTraceUtils
.
Profiling
.
recordExit
(
profiler
,
probeMethod
,
duration
);

}

@OnTimer
(
5000
)

void
timer
()

{

BTraceUtils
.
Profiling
.
printSnapshot
(
"Performance profile"
,
profiler
);

}
死锁排查

我们怀疑程序是否有死锁,可以通过以下的脚本扫描追踪,非常简单方便。
/**

  • This BTrace program demonstrates deadlocks
  • built-in function. This example prints
  • deadlocks (if any) once every 4 seconds.
    */

@BTrace
public

class

PrintDeadlock

{

@OnTimer
(
4000
)

public

static

void
print
()

{

    deadlocks

();

}
}
小结

BTrace是一个事后工具,所谓的事后工具就是在服务已经上线或者压测后,但是发现有问题的时候,可以使用BTrace动态跟踪分析。

比如哪些方法执行太慢,例如监控方法执行时间超过1秒的方法;
查看哪些方法调用了system.gc( ),调用栈是怎样的;
查看方法的参数和属性
哪些方法发生了异常
.....
总之,这里只是将部分经常用的列举了下抛砖引玉,还有很多没有列举,大家可以参考官方的其他Sample去玩下。

以上是关于性能工具之Java调试工具BTrace入门的主要内容,如果未能解决你的问题,请参考以下文章

生产环境中系统调试--BTrace

性能工具之Java调试工具JDB

性能工具之 Java 调试工具 JDB

性能分析之代码调试-动态修改内存变量值(C/C++)

利用jvisualvm使用btrace进行线上调试案例

BTrace