在測試腳本編寫和應用部署時,經常遇到的一 個問題是:java.lang.NoSuchMethodError。這個問題産生的根本原因是運作時應用加載的jar包版本不是應用代碼真正需要的版本。要解決這個問題,就要讓應用加載真正“HasSuchMethod"的類所在的jar包。解決這個問題,我把它歸納為以下幾步:驗證加載内容、查找包含該類的jar包、查找應用适用的jar版本、檢視出錯應用加載的jar包位置、替換錯誤jar包版本到需要的版本。 驗證加載内容
通常,發生了NoSuchMethodError,在報錯中我們可以檢視到應用真實需要的方法和方法簽名,例如如下的報錯: java.lang.NoSuchMethodError: com.taobao.eagleeye.EagleEye.rpcClientRecv(Ljava/lang/String;I)V 表明應用需要的是EagleEye類中的包含方法簽名如:void rpcClientRecv(String str,int i)的方法。知道了這一點,我就自然需要去看一下應用中實際加載的EagleEye是什麼樣的情況,rpcClientRecv方法的簽名是什麼樣子的。 要得到這些内容,可以用兩個工具來擷取,1是clas sdump工具,它通過Serviceability Agent技術在運作時dump指定的類的位元組碼檔案。2是jdgui,這是使用得最多的反編譯工具,它可以把clas sdump工具dump出來的位元組碼檔案反編譯成java代碼,可以更直覺得檢視應用中加載的類是什麼樣子。 clas sdump工具的使用: 指令:java -classpath ".:/opt/taobao/java/lib/sa-jdi.jar" sun.jvm.hotspot.tools.jcore.ClassDump 16528 其中-classpath把sa-jdi的jar包加入到classpath中,ClassDump 是執行位元組碼檔案dump的main方法入口類,16528是目标程序的pid,可以用jps指令得到。 這樣,ClassDump會把目前加載的所有Java類都dump到目前目錄下,如果有全限定名相同但内容不同的類同時存在于一個Java程序中,那麼dump的時候會有覆寫現象,實際dump出來的是同名的類的最後一個。 如果需要指定被dump的類的範圍,可以自己寫一個過濾器,在啟動ClassDump工具時指定-Dsun.jvm.hotspot.tools.jcore.filter=filterClassName,如果需要指定dump出來的Class檔案的存放路徑,可以用-Dsun.jvm.hotspot.tools.jcore.outputDir=path來指定,path替換為實際路徑。 對于上面講的 例子,我們隻需要 EagleEye類的内容,那麼,就可以寫一個FilterClass類來進行dump的過濾,寫這個過濾類,需要實作sun.jvm.hotspot.tools.jcore.ClassFilter,具體實作如下圖所示,InstanceKlass對應于HotSpot中表示Java類的内部對象,我們隻要過濾類名以com/taobao/eagleeye的類就好。
寫好了這個類之後,編譯,把這個類的class檔案放到要執行ClassDump指令的目錄下,執行時增加如下參數:-Dsun.jvm.hotspot.tools.jcore.filter=MyFilter,ClassDump就會在目前目錄dump出com.taobao.eagleeye.EagleEye類的位元組碼。 然後使用jd-gui,jd-gui是一個綠色工具,直接打開位元組碼檔案,可以看到rpcClientRecv方法,這裡就可以看到,實際上目前jvm加載的是一個沒有參數的rpcClientRecv方法。
查找包含該類的jar包、查找應用适用的jar版本 确認了目前出現錯誤的應用中确實加載了不符合要求的類之後,就需要去看一下,這個類是從哪裡來的,在哪個jar包中。做這件事情比較簡單,隻需要去eclipse中執行ctrl+shift+t,輸入類名,就可以看到目前eclipse的工程中依賴的各個版本的包含這個類的jar包。接下來你可以把各個版本的類都打開,檢查這些版本的jar包中是否有符合要求的方法,即方法簽名符合rpcClientRecv(Ljava/lang/String;I)V的方法。方法簽名識别:其中L開頭,;結尾的字元串辨別了一個對象類型,這裡的第一個參數是java.lang.String類型,第二個參數是int類型,V表示void類型傳回。詳細的jvm中字段類型和方法簽名表示見下圖:
通過這個步驟,我們知道了實際上我們的應用需要什麼版本的jar包,那麼,接下來自然就會想要知道應用目前加載的是什麼版本的jar包,又是在哪裡加載進來的。
檢視出錯應用加載的jar包和jar包位置 在這個步驟,要想知道應用加載的是jar包的什麼版本,需要使用jvm類加載跟蹤器這個工具,這是一個基于java agent、instrument的,用于排查jar包沖突、類沖突、類版本沖突、NoClassDefFoundError、ClassNotFoundException、NoSuchMethodError 等類加載相關問題的輔助工具。這個工具的能夠定時得把新加載入虛拟機類個jar包等資料輸出到指定的html檔案中。 工具的使用方式是:首先,下載下傳得到 jvminspect.jar;其次,修改目标應用的jvm配置,在jvm參數上增加如下的配置: JAVA_OPTIONS=$JAVA_OPTIONS -javaagent:jvminspect.jar=outputfile=jvm.inspect.output,flushIntervalSecond=300 -DHtmlFlusher.enableHyperlink=false;其中jvminspect.jar是下載下傳得到的工具jar包,jvm.inspect.output是輸出的檔案位址,flushIntervalSecond參數指定的是定時重新整理的時間間隔。增加了這個參數之後重新開機,就可以在jvm.inspect.output檔案中檢視這個jvm程序加載類的情況了。 我們關心的是出現NoSuchMethodError 所在的類,在這個例子中,這是com.taobao.eagleeye.EagleEye類,在輸出的檔案中我們可以看到這個類被jvm從jar包的哪個版本中加載,jar包的實體位置等資訊。
知道了這些資訊,我們就很清楚得能夠确定問題的原因。 替換錯誤jar包版本到需要的版本 知道應用在那裡加載了不期望被加載的jar包,我們就可以着手讓應用把需要加載的正确版本包加載進來。不同的場景可以做不一樣的操作,比如應用的war包中本身加載了錯誤版本的jar包,那你需要做的是修改應用的pom檔案進行重新打包,把正确的jar包依賴到war包中;如果是由于某些環境的原因,classpath中存在了兩個不同版本的jar包,那你就可以根據前面幾步得到的資訊,把錯誤版本(導緻應用報NoClassDefFoundError、ClassNotFoundException、NoSuchMethodError等錯)的版本删除,留下應用代碼實際上依賴的jar包。