引言
在咱们对 Java 利用做问题剖析的时候,往往采纳 log 进行问题定位和剖析,然而如果咱们的 log 不足相干的信息呢?近程调试会影响利用的失常工作,批改代码重新部署利用,实时性和灵活性难以保障,有没有不影响失常利用运行,又灵便并无侵入性的办法呢?
答案是有,它就是 Java 中的神器 -BTrace
BTrace 是什么?
BTrace 应用 Java 的 Attach 技术,能够让咱们无缝的将咱们 BTrace 脚本挂到 JVM 上,通过脚本你能够获取到任何你想拿到的数据,在侵入性和安全性都十分牢靠,特地是定位线上问题的神器。
BTrace 原理
BTrace 是基于动静字节码批改技术(Hotswap)向目标程序的字节码注入追踪代码。
装置配置
对于 BTrace 的装置配置应用,此处就不再反复造轮子,网上有太多的教程。
官网地址:https://github.com/btraceio/btrace
注意事项
生产环境能够应用,但批改的字节码不会被还原,应用 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 去玩下。