BTrace是Java的安全可靠的動態(tài)跟蹤工具。 他的工作原理是通過 instrument + asm 來對正在運行的java程序中的class類進行動態(tài)增強, 加入檢測代碼在運行時對應(yīng)用進行分析和跟蹤。
當線上應(yīng)用拋出一個異常柑爸,我們該如何使用btrace進行分析呢服球?
異常堆棧
java.io.NotSerializableException: com.xxx.UserServiceImpl
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at de.javakaffee.web.msm.JavaSerializationTranscoder.writeAttributes(JavaSerializationTranscoder.java:139)
at de.javakaffee.web.msm.JavaSerializationTranscoder.serializeAttributes(JavaSerializationTranscoder.java:100)
at de.javakaffee.web.msm.TranscoderService.serializeAttributes(TranscoderService.java:151)
at de.javakaffee.web.msm.BackupSessionTask.serializeAttributes(BackupSessionTask.java:179)
at de.javakaffee.web.msm.BackupSessionTask.call(BackupSessionTask.java:109)
at de.javakaffee.web.msm.BackupSessionTask.call(BackupSessionTask.java:50)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
使用btrace腳本進行運行時檢測
根據(jù)異常堆棧信息伐庭,對發(fā)生異常的代碼進行探測。location=@Location(Kind.ERROR) 表明 在發(fā)生未被捕獲的異常結(jié)束時執(zhí)行霸株。
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace public class WriteObjectErrDetect {
@OnMethod(
clazz="java.io.ObjectOutputStream",
method="writeObject0",
location=@Location(Kind.ERROR)
)
public static void detect(@ProbeClassName String probeClass, @ProbeMethodName String probeMethod,@TargetInstance Throwable err
,Object obj, boolean unshared) {
print("#####1");
println(str(obj));
}
}
控制臺輸出:
#####org.springframework.security.core.context.SecurityContextImpl@29fefc4f
#####com.xxx.UserServiceImpl@10681811
從輸出看基本上可以斷定是SecurityContextImpl類的對象引用了UserServiceImpl對象去件,導(dǎo)致序列化失敗尤溜。因為UserServiceImpl對象不可序列化宫莱。
結(jié)合應(yīng)用代碼邏輯哩罪,輸出對象屬性看看:
import java.lang.reflect.Field;
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace public class WriteObjectErr {
@OnMethod(
clazz="java.io.ObjectOutputStream",
method="writeObject",
location=@Location(Kind.ERROR)
)
public static void detect(@ProbeClassName String probeClass, @ProbeMethodName String probeMethod,@TargetInstance Throwable err
,Object obj) {
println("#####1");
println(str(obj));
printFields(obj);
println("");
Field field = field(classOf(obj), "authentication", false);
if(field!=null) {
println("#####2");
Object auth = get(field, obj);
printFields(auth);
println("");
}
}
}
控制臺輸出
#####1
org.springframework.security.core.context.SecurityContextImpl@109497a3
{authentication=com.xxx.UserServiceImpl$3@4cdcf97c, }
#####2
{val$loginUser=com.xxx.User@2dc609e6, this$0=com.xxx.UserServiceImpl@10681811, }
從輸出可以看到际插,對象中屬性this$0引用了UserServiceImpl對象,證明了之前的猜想框弛。
this$0是什么屬性功咒? 它其實是匿名類或非靜態(tài)內(nèi)部類對外部對象的應(yīng)用。因此外部對象不可序列化力奋,導(dǎo)致了該序列化異常。
注意:在對線上環(huán)境進行分析的時候溅呢,最好在測試環(huán)境對btrace腳本進行驗證咐旧。btrace腳本有可能會導(dǎo)致線上jvm進程異常退出铣墨。此次就碰到了伊约, 在使用location=@Location(Kind.ERROR)的時候, 方法簽名上沒有@TargetInstance Throwable err腌逢,直接導(dǎo)致jvm進程退出了搏讶。