Java調優(yōu)系列之工具篇之btrace、gperftools
landon 網絡游戲資深服務器架構師
2018-06-14
線上遇到了問題昭抒?
- 服務上線出問題评也,想增加打印日志怎么辦?
- 線上懷疑某個接口慢灭返,想打印接口耗時怎么辦盗迟?
- 線上某個接口報錯,想看看調用的參數和誰調用了怎么辦熙含?
- 線上出錯了罚缕,想看某個對象的數據怎么辦?
- 線上出錯了怎静,想看一下jvm的一些信息怎么辦邮弹?
- 不確定線上某一行代碼執(zhí)行了怎么辦?
- ......
傳統(tǒng)解決方案
- 修改源代碼 -> 增加相關打印日志 -> hotswap
- Thread.dumpStack
- beanshell可以查看內存數據
- jvm信息可以通過jvm內置命令去獲取
- 缺點
- 代碼侵入式
- 不靈活
- 源代碼冗余
- 如果你的服務不支持hotswap呢蚓聘?
什么是btrace
- BTrace is a safe, dynamic tracing tool for the Java platform.
- BTrace can be used to dynamically trace a running Java program (similar to DTrace for OpenSolaris applications and OS). BTrace dynamically instruments the classes of the target application to inject tracing code ("bytecode tracing").
- 江南白衣
- Btrace是神器腌乡,每一個需要每天解決線上問題,但完全不用Btrace的工程師夜牡,都是可疑的
btrace基礎
-
https://github.com/btraceio/btrace
- 以前sun開源的項目
- 下載
- bin 加入環(huán)境變量
- build 實現(xiàn)包和依賴包
- samples 示例腳本
- docs 幫助文檔
- 基礎使用
- IDE下編寫btrace腳本
- jps找到運行的java進程pid
- btrace pid btracescript
btrace腳本編寫基礎事項
- IDE編寫要引入依賴jar
- btrace-agent.jar
- btrace-boot.jar
- btrace-client.jar
- 腳本就是一個.java源文件
- @BTrace注解
- trace的class要用full name
- 只能使用btrace提供的方法与纽,不能自己隨意調用(保證性能不受影響,trace才更放心)
btrace實戰(zhàn)
- 一個簡單的偏游戲業(yè)務的sample
- btrace
- Locate 定位trace的方法
- Intercept 對定位到的方法進行攔截
- Print 打印需要的數據
- 典型場景
- 找出最耗時的業(yè)務方法
- 打印調用堆棧
- ......
btrace#locate
- TraceLocate.java
- 舉例
- @OnMethod(clazz = "/practice./", method = "/./")
- @OnMethod(clazz = "+practice.IHandler", method = "/.*/")
- @OnMethod(clazz = "/practice.*/", method = "@practice.LogicMethod")
- @OnMethod(clazz = "/practice.*/", method = "<init>")
- 看示例代碼
btrace#intercept
- TraceIntercept.java
- 舉例
- @OnMethod(clazz = "practice.LoginHandler", method = "login", location = @Location(Kind.ENTRY))
- @OnMethod(clazz = "practice.LoginHandler", method = "login", location = @Location(Kind.RETURN))
- @OnMethod(clazz = "+practice.IHandler", method = "/.*/", location = @Location(Kind.THROW))
- @OnMethod(clazz = "practice.LoginHandler", method = "login", location = @Location(value = Kind.CALL, clazz = "/./", method = "/./", where = Where.AFTER))
- @OnMethod(clazz = "practice.HeroHandler", method = "starUp", location = @Location(value = Kind.LINE, line = 16))
- 看示例代碼
btrace#print
- TracePrint.java
- 舉例
- printMethodSignature
- AnyType
- Field field = BTraceUtils.field("practice.PlayerService", "playerMap")
- @TLS thread local share
- 看示例代碼
btrace#TypicalScenes
- TraceTypicalScenes.java
- 舉例
- @Duration long duration
- 納秒
- Kind.RETURN
- BTraceUtils.jstack
- OnTimer
- jvm
- jinfo/jmap/...
- sizeof/deadlock
- @Duration long duration
- 示例代碼和自帶sample
btrace原理
- Client(Java compile api + attach api) + Agent(腳本解析引擎 + ASM + JDK6 Instumentation) + Socket
- java attach api附加agent.jar + 腳本解析引擎+asm來重寫指定類的字節(jié)碼 + instrument實現(xiàn)對原有類的替換
- 通過JVM Attach API塘装,btrace把自己綁進了被監(jiān)控的進程 -> 按照腳本定義 -> AOP代碼植入
btrace原理圖解
btrace注意的問題
- 限制
- 為了保證性能不受影響急迂,Btrace不允許調用任何實例方法,必須使用btrace提供的api
- BTrace植入過的代碼蹦肴,會一直在
- 屬于“事后工具” 袋毙,即服務已經上線了,無法再通過打印日志等方式埋點分析
- 無法trace native
- 一定要用IDE寫btrace腳本
- 其他
- 有一些腳本細節(jié)需要自行體會 如追蹤異常的上下文
- 可以遠程冗尤、可用于線上生成環(huán)境 還有許多其他命令參數如指定文件輸出
其他工具和參考
- 其他類似工具(可能更強大 但是我個人還是偏向sun的btrace)
- 參考
gperftools
- 場景
- 主要分析Java的堆外內存泄露
- Java進程占用內存
- Note that the JVM uses more memory than just the heap. For example Java methods, thread stacks and native handles are allocated in memory separate from the heap, as well as JVM internal data structures
- 堆外內存
- jni
- nio direct buffer
gperftools#install
- 下載地址
- install
- Perftools was developed and tested on x86 Linux systems, and it works in its full generality only on those systems. However, we've successfully ported much of the tcmalloc library to FreeBSD, Solaris x86, and Darwin (Mac OS X) x86 and ppc; and we've ported the basic functionality in tcmalloc_minimal to Windows. See INSTALL for details.See README_windows.txt for details on the Windows port.
- 強烈建議直接在linux安裝听盖,mac文檔少,windows不考慮
- linux注意問題
- https://github.com/gperftools/gperftools/blob/master/INSTALL
- if you use a 64-bit system, we strongly recommend you install libunwind before trying to configure or install gperftools
- 動態(tài)庫配置(ld.so.conf.d/ldconfig)裂七、root
堆外內存泄露sample
- NonHeapLeakExample.java
- java.util.zip.Deflater
- javac -> jar -> run.sh
- javac NonHeapLeakExample.java
- jar cvf NonHeapLeak.jar NonHeapLeakExample.class
- java -cp ./NonHeapLeak.jar NonHeapLeakExample
- top/jmap/jstat
- 示例代碼和腳本
gperftools排查
- java啟動腳本增加參數
- export LD_PRELOAD=/usr/local/lib/libtcmalloc.so
- export HEAPPROFILE=/tmp/nonheapleak
- 啟動輸出
- Starting tracking the heap
- 查看heap是否生成
- $ ll /tmp | grep .heap
- -rw-rw-r-- 1 playcrab playcrab 1048574 Jun 13 16:24 nonheapleak_28759.0001.heap
- 分析heap
- pprof --text $JAVA_HOME/bin/java /tmp/nonheapleak_28759.0001.heap
gperftools結果展示
- 結果展示
Using local file /data/home/user00/playcrab/usr/jdk/bin/java.
Using local file /tmp/nonheapleak_28759.0003.heap.
Total: 300.2 MB
281.9 93.9% 93.9% 281.9 93.9% deflateInit2_
17.7 5.9% 99.8% 17.7 5.9% os::malloc@907360
0.3 0.1% 99.9% 0.3 0.1% readCEN
0.1 0.0% 100.0% 282.0 94.0% Java_java_util_zip_Deflater_init
......
- 分析
- Java_java_util_zip_Deflater_init是一個jni方法 對應java方法
- 可以看到這塊占用內存占用很大
如何找到誰調用了Deflater皆看?
- btrace派上用場
@OnMethod(clazz = "java.util.zip.Deflater", method = "<init>")
public static void traceStack() {
BTraceUtils.jstack();
}
- 有了Java的調用堆棧,就好辦了
- 原因
- deflater沒有調用end
- deflater的底層實現(xiàn)全部是調用jni
- 只申請了內存但是沒有釋放
源代碼
Q & A
- 演示過程中背零,貌似gperftools的heap沒生成腰吟,進程kill的時候生成了
- 可以生成 不過得等一下
- 從輸出看 是差不多6分鐘左右 heap文件才生成
$ date
2018年 06月 14日 星期四 22:39:43 CST
[playcrab@achilles landon]$ sh nonHeapLeak.sh
$ ll /tmp/*.heap
-rw-rw-r-- 1 playcrab playcrab 1048562 6月 14 22:45 /tmp/nonheapleak_10821.0001.heap
-rw-rw-r-- 1 playcrab playcrab 1048563 6月 14 22:51 /tmp/nonheapleak_10821.0002.heap
- 關于btrace的注入問題
1. btrace注入的代碼確實一直都在
2. 沒有辦法移除 因為再次寫腳本也是在注入后的基礎上
3. 通常來說線上排查問題 我們不會寫如定位線上所有的業(yè)務方法都進行攔截 肯定會有選擇性的 另外和源代碼增加日志一樣 不會有什么性能問題
4. btraceutils.println是直接發(fā)送到了attach的agent的buff(socket)沒什么問題,client退出了就不會發(fā)了徙瓶,只不過代碼還依然執(zhí)行而已 landon-本質還是socket通訊