arthas用的好好的,写个lambda表达式就跪了?该咋解决?
△Hollis, 一個(gè)對(duì)Coding有著獨(dú)特追求的人△
這是Hollis的第?278?篇原創(chuàng)分享
作者 l Robert Lu
來源 l Hollis(ID:hollischuang)
作為一個(gè)從PHP轉(zhuǎn)Java的人,發(fā)現(xiàn)alibaba的arthas很好用。通過arthas的redefine命令,可以像PHP一樣,不用重新發(fā)布,就可以改變程序行為(前提是不改變類結(jié)構(gòu),不改變方法簽名)。
但是用多了,發(fā)現(xiàn)很多時(shí)候,我們就改了幾行代碼,甚至有的時(shí)候就添加了一行日志,就無法redefine了。提示
redefine error! java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method
它提示我們新增加方法,那我們就看看是不是新增加了方法。通過javap來查看定義的方法:
這是老的類:
這是新的類:
對(duì)比之后發(fā)現(xiàn),新的類,即本地編譯的類,其中的lambda對(duì)應(yīng)的方法名都是lambda$getAllCity$0這樣的,最后的編號(hào)是從0開始的。
而舊的類,即現(xiàn)在在運(yùn)行的類,其中的同一個(gè)lambda的方法名是lambda$getAllCity$121,最后的編號(hào)是一個(gè)非常大的數(shù)字。
在仔細(xì)對(duì)比下,發(fā)現(xiàn)是jdk的版本問題,不同的jdk版本對(duì)與lamdba的處理可能不一致。
具體來說,線上編譯的jdk版本是1.8.0_66-b17, 而本地是1.8.0_222-b10,而這兩個(gè)版本對(duì)lambda對(duì)應(yīng)的方法命名是不一樣的。
首先,為了調(diào)試方便,寫一個(gè)最小復(fù)現(xiàn)用例來看看:
// Compile.java // 編譯LamdbaTest1.java和LamdbaTest2.java import javax.tools.*; import java.io.File; public class Compile { public static void main(String[] args) { String path1 = "/path/to/LamdbaTest1.java"; String path2 = "/path/to/LamdbaTest2.java";JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();DiagnosticCollector diagnostics = new DiagnosticCollector();StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(diagnostics, null, null);Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects( new File(path1), new File(path2));JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, diagnostics, null, null,compilationUnits); boolean success = task.call();System.out.println(success);} } //LamdbaTest1.java public class LamdbaTest1 {private void test(Runnable runnable) {runnable.run();}private void main() throws Throwable {test(() -> {System.out.println(11);});} } //LamdbaTest2.javapublic class LamdbaTest2 { private void test(Runnable runnable) { runnable.run(); } private void main() throws Throwable { test(() -> { System.out.println(22); }); }}使用1.8.0_222-b10(新版本jdk)跑完了之后,發(fā)現(xiàn)LamdbaTest2中的lambda方法是:
private static void lambda$main$0();而換版本1.8.0_66-b17(舊版本jdk)之后,lambda的方法就成了:
private static void lambda$main$1();多嘗試幾個(gè)文件同時(shí)編譯,我們就可以發(fā)現(xiàn):對(duì)于舊版本的javac,末尾這個(gè)數(shù)字是全局遞增的,50個(gè)類有100個(gè)lambda,那最后一個(gè)lambda的編號(hào)就是99;而新的版本是每個(gè)類重新計(jì)數(shù)的,和總共多少個(gè)類沒有關(guān)系。
確認(rèn)了問題之后,接下來就是不斷的打斷點(diǎn)、重試了。后來發(fā)現(xiàn)不同版本的javac邏輯確實(shí)不同。
首先,查看jdk源碼可以知道,lambda的方法名都是:
lambda$<methodname>$<lambdaCount>
不同的地方在于:新版本的javac,在處理一個(gè)新的類的時(shí)候,會(huì)保存上一個(gè)lambdaCount,后續(xù)再恢復(fù),在當(dāng)前類,則直接從0開始重新計(jì)數(shù):
而舊版本則沒有這個(gè)邏輯,直接用全局遞增的計(jì)數(shù)器:
這就說明舊版本的編譯器確實(shí)是lambda全局編號(hào)的。
那,問題來了,這個(gè)行為是從哪個(gè)版本變掉的呢?
對(duì)比之后發(fā)現(xiàn)這個(gè)變更是jdk8u74-b02引入的。對(duì)應(yīng)的bug是https://bugs.openjdk.java.net/browse/JDK-8067422
基本上就是每個(gè)類內(nèi)的lambda單獨(dú)編號(hào),確保編譯順序不會(huì)影響lambda的方法名字。
所以,解決方案很簡(jiǎn)單,升級(jí)編譯環(huán)境的jdk版本就好。
非常巧合的是,前兩天為了更好的適配Docker運(yùn)行環(huán)境(通俗的講,就是在容器內(nèi)獲取到docker的cpu配額,而不是物理機(jī)器的cpu數(shù)量),我找運(yùn)維添加了一個(gè)新的jdk版本1.8.0_231-b11,這樣只需要直接將編譯環(huán)境的jdk版本切換到8u231就行了!
Arthas官方正在舉辦征文活動(dòng),如果你有
使用 Arthas 排查過的問題
對(duì)?Arthas 進(jìn)行源碼解讀
對(duì)?Arthas 提出建議
不限,其它與 Arthas 有關(guān)的內(nèi)容
? ?
歡迎參加征文活動(dòng),還有獎(jiǎng)品拿哦~
? ?
點(diǎn)擊閱讀原文接入征文活動(dòng)地址。
福利時(shí)間
GIFT TIME
Arthas 技術(shù)社區(qū)特意將本次征文活動(dòng)的一部分禮品(1個(gè)阿里云定制雨傘,2個(gè)淘公仔,5個(gè)解壓腦型球)贊助了給到Hollis的讀者
本次禮品不需要評(píng)論,也不需要轉(zhuǎn)發(fā)
采用最公平的抽獎(jiǎng)方式。
關(guān)注公眾號(hào):程序員面試現(xiàn)場(chǎng)
公眾號(hào)后臺(tái)回復(fù):征文?
即可參與抽獎(jiǎng)
往期推薦漫話:為什么鍵盤以QWER排列,而不是ABCD?
如何讓程序員變得沒朋友
平滑從Consul遷移到 Nacos,再也不用擔(dān)心特朗普發(fā)瘋了...
?
直面Java第320期:如何判斷一個(gè)Java對(duì)象能否被回收?
深入并發(fā)第013期:拓展synchronized——鎖優(yōu)化
如果你喜歡本文,
請(qǐng)長(zhǎng)按二維碼,關(guān)注?Hollis.
轉(zhuǎn)發(fā)至朋友圈,是對(duì)我最大的支持。
點(diǎn)個(gè)?在看?
喜歡是一種感覺
在看是一種支持
↘↘↘
總結(jié)
以上是生活随笔為你收集整理的arthas用的好好的,写个lambda表达式就跪了?该咋解决?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你还不了解Redis的发布/订阅功能与R
- 下一篇: Mysql索引扫盲总结