【得物技術】服務發佈時網絡“抖動”

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"拋出問題","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服務部署後一段時間內經常會遇見接口調用超時,這種問題在流量稍大的時候很容易遇見,舉例曾經做過的一個服務,整個服務只對外提供一個接口,功能屬於密集計算型,會經過一系列的複雜處理,實例啓動時藉助Redis實現了數據的全量緩存,運行中很少有底層庫的讀寫,且增加了Gauva本地緩存,所以服務處理速度很快,響應時間穩定在1-3ms,2個異地集羣8臺2核4G機器,平時的QPS維持在1300左右。看似完美的服務卻在中期出現一個很頭疼的問題,每次迭代線上部署上游總會出現“抖動”的問題,響應時間會飆升到秒級別,而這種非主鏈路的服務在響應時間上有嚴格的要求,一般不會超過50~100ms。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ee/eed33880b6194d654458cb5f67c65e62.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"解決歷程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏簡單說下解決問題的歷程,不再詳細展開具體步驟。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"1.嘗試第一步","attrs":{}},{"type":"text","text":" 起初猜想是服務啓動後的一段時間內會有一些初始化和緩存的操作,或者服務還有序列化器需要建立請求和響應的序列化模型,這些都會讓請求變慢,所以嘗試在流量切入之前做預熱。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.嘗試第二步","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7c/7c9f6980bfad2988e31a22b2f28743dc.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fa/fa35f262d717b8ed360dd0462ee408df.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/00/008db58a390790181bed40b534f95620.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過以上指標可以發現服務在部署的時候CPU飆升,內存還算正常,活躍線程數飆高,不難看出,CPU飆升是因,Http線程數飆升是果,此時可以先忽略CPU在忙什麼,通過加機器配置排查問題。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"3.嘗試第三步","attrs":{}},{"type":"text","text":" 走到這裏就說明前面嘗試都以失敗告終,無頭緒的時候又仔細翻看下監控指標,發現還有一個異常指標,如下圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/76/76513c49935282583d503afeaf1383a9.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JVM的即時編譯器時間達到40s+,從而導致CPU飆高,線程數增加,最後出現接口超時也就不難理解了。找到原因接下來就三部曲分析下,是什麼,爲什麼,怎麼做?","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"即時編譯器是什麼","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"1. 概述","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"編程語言分爲高級語言和低級語言,機器語言和彙編語言屬於低級語言,人類編寫的一般是高級語言,列如C,C++,Java。機器語言是最底層的語言,能夠被計算機直接執行,彙編語言通過彙編器翻譯成機器執行然後執行,高級語言的執行類型有三種,編譯執行、解釋執行和編譯解釋混合模式,Java起初被定義爲解釋執行,後來主流的虛擬機都包含了即時編譯器,所以Java是屬於編譯和解釋混合模式,首先通過javac將源碼編譯成.class字節碼文件,然後通過java解釋器解釋執行或者通過即時編譯器(下文中簡稱JIT)預編譯成機器碼等待執行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/86/86a38288647d078a253f67bff3ab83c4.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JIT並不是JVM必須的部分,JVM規範中也沒有規定JIT必須存在,更沒有規定如何實現,它存在的主要意義在於提高程序的執行性能,根據“二八定律”,百分之二十的代碼佔用百分之八十的資源,我們稱這些百分之二十的代碼爲“熱點代碼”(Hot Spot Code),針對熱門代碼我們用JIT編譯成與本地平臺相關的機器碼,並進行各層次的深度優化,下次使用時不再進行編譯直接取編譯後的機器碼執行。而針對非“熱點代碼”使用相對耗時的解釋器執行,將我們的代碼解釋成機器可以識別的二進制代碼,解釋一句執行一句。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2. 解釋器和編譯器對比","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"解釋器:邊解釋邊執行,省去編譯的時間,不會生成中間文件,不用把全部代碼都編譯,可以及時執行。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"編譯器:多出編譯的時間,並且編譯後的代碼大小會成倍的膨脹,但編譯後的可執行文件運行更快且能複用。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們所說的JIT比解釋器快,僅限於“熱點代碼”被編譯之後的執行比解釋器解釋執行快,如果只是單次執行的代碼,JIT是要比解釋器慢的。根據兩者的優勢,對於服務要求快速重啓或者內存資源限制較大的解釋器可以發揮優勢,程序運行後,編譯器將逐漸發揮作用,把越來越多的“熱門代碼”編譯成本地代碼來提高效率。此外,解釋器還可以作爲編譯器進行優化的一個“逃生門”,當激進優化的假設不成立時可以通過逆優化退回到解釋器狀態繼續執行,兩者能夠相互協作,取長補短。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"3. 即時編譯器的類型","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在HotSpot虛擬機中,內置了兩個JIT:Client Complier和Server Complier,簡稱C1、C2編譯器。前面提到JVM是屬於解釋和編譯混合的模式,程序使用哪個編譯器取決於虛擬機的運行模式,虛擬機會根據自身的版本與宿主機的硬件性能自動選擇,用戶也可以使用“-client”或者“-server”參數指定使用哪個編譯器。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"C1編譯器:是一個簡單快速的編譯器,主要的關注點在於局部性的優化,採用的優化手段相對簡單,因此編譯時間較短,適用於執行時間較短或對啓動性能有要求的程序,比如GUI應用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"C2編譯器:是爲長期運行的服務器端應用程序做性能調優的編譯器,採用的優化手段相對複雜,因此編譯時間較長,但同時生成代碼的執行效率較高,適用於執行時間較長或對峯值性能有要求的程序。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java7引入了分層編譯,這種方式綜合了C1的啓動性能優勢和C2的峯值性能優勢。分層編譯將JVM的執行狀態分爲了5個層次:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第0層:程序解釋執行,默認開啓性能監控功能(Profiling),如果不開啓,可觸發第1層編譯。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第1層:可稱爲C1編譯,將字節碼編譯爲本地代碼,進行簡單、可靠的優化,不開啓Profiling。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第2層:也稱爲C1編譯,開啓Profiling,僅執行帶方法調用次數和循環回邊執行次數Profiling的C1編譯。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第3層:也稱爲C1編譯,執行所有帶Profiling的C1編譯。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第4層:可稱爲C2編譯,也是將字節碼編譯爲本地代碼,但是會啓用一些編譯耗時較長的優化,甚至會根據性能監控信息進行一些不可靠的激進優化。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9d/9d1270f247e21314b31f6944a6fcf89c.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Java8中默認開啓分層編譯(-XX:+TieredCompilation),-client和-server的設置已經是無效的了。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果只想用C1,可以在打開分層編譯的同時使用參數“-XX:TieredStopAtLevel=1”。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果只想用C2,使用參數“-XX:-TieredCompilation”關閉分層編譯即可。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果只想有解釋器的模式,可以使用“-Xint”,這時JIT完全不介入工作。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果只想有JIT的編譯模式,可以使用“-Xcomp”,此時解釋器作爲編譯器的“逃生門”。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過java -version命令行可以直接查看到當前系統使用的編譯模式。如下圖所示:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d3/d378586e8b185e610f17dfe0fbed964f.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"4.熱點探測","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"即時編譯器判斷某段代碼是不是“熱點代碼”的行爲叫做熱點探測,分爲基於採樣的熱點探測和基於計數器的熱點探測。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基於採樣的熱點探測:虛擬機週期性的對各個線程的棧頂進行檢查,如果發現某個方法經常出現那這個方法就是“熱點代碼”,這種熱點探測實現簡單、高效、容易獲取方法之間的調用關係,但很難精確的確認方法的熱度,而且容易受到線程阻塞或其他外因的干擾。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基於計數器的熱點探測:虛擬機爲每個方法或代碼塊建立計數器,統計代碼的執行次數,如果超過一定的閾值就認爲是“熱點代碼”,這種方式實現麻煩,不能直接獲取方法間的調用關係,需要爲每個方法維護計數器,但是能精確統計熱度。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HotSpot虛擬機默認是基於計數器的熱點探測,因爲“熱點代碼”分爲兩類:被多次調用的方法和被多次執行的循環體,所以該熱點探測計數器又分爲方法調用計數器和回邊計數器。這裏需要注意,兩者最終編譯的對象都是完整的方法,對於後者,儘管觸發編譯的動作是循環體,但編譯器依然會編譯整個方法,這種編譯方式因爲編譯發生在方法的執行的過程中,因此稱之爲棧上替換(On Stack Replacement, 簡稱OSR編譯)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"方法調用計數器:用於統計方法被調用的次數,在C1模式下默認閾值是1500次,在C2模式在是10000次,可通過參數-XX: CompileThreshold來設定。而在分層編譯的情況下,-XX: CompileThreshold指定的閾值將失效,此時將會根據當前待編譯的方法數以及編譯線程數來動態調整,當方法計數器和回邊計數器之和超過方法計數器閾值時,就會觸發JIT編譯器,觸發時執行引擎並不會同步等待編譯的完成,而是繼續按照解釋器執行字節碼,編譯請求完成之後系統會把方法的調用入口改成最新的地址,下一次調用時直接使用編譯後的機器碼。 ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果不做任務的設置,方法調用計數器統計的並不是絕對次數,而是一個相對的執行頻率,即一段時間內被調用的次數,當超過時間限度調用次數仍然未達到閾值,那麼該方法的調用次數就會減半,此行爲稱之爲方法調用計數器熱度的衰減,這段時間稱爲此方法的統計半衰週期,可以使用虛擬機參數-XX:-UseCounterDecay關閉熱度衰減,也可以使用參數-XX:CounterHalfLifeTime設置半衰週期的時間,需要注意進行熱度衰減的動作是在虛擬機進行垃圾收集時順便進行的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/46/464a78c8d90f51bba23d0218e193cdb6.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回邊計數器:用於統計一個方法中循環體代碼執行的次數,在字節碼中遇到控制流向後跳轉的指令稱爲回邊。與方法計數器不同,回邊計數器沒有計數熱度衰減的過程,統計的是方法循環執行的絕對次數。當計數器溢出時會把方法計數器的值也調整到溢出狀態,這樣下次再進入該方法時就會執行編譯過程。在不開啓分層編譯的情況下,C1默認爲13995,C2默認爲10700,HotSpot提供了-XX:BackEdgeThreshold來設置回邊計數器的閾值,但是當前虛擬機實際上使用-XX: OnStackReplacePercentage來間接調整。 ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Client模式下:閾值=方法調用計數器閾值(CompileThreshold)*OSR比率(OnStackReplacePercentage)/ 100。默認值CompileThreshold爲1500,OnStackReplacePercentage爲933,最終回邊計數器閾值爲13995。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Server模式下:閾值=方法調用計數器閾值(CompileThreshold)*(OSR比率(OnStackReplacePercentage)-解釋器監控比率(InterpreterProfilePercentage))/ 100。默認值CompileThreshold爲10000,OnStackReplacePercentage爲140,InterpreterProfilePercentage爲33,最終回邊計數器閾值爲10700. ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而在分層編譯的情況下,-XX: OnStackReplacePercentage指定的閾值同樣會失效,此時將根據當前待編譯的方法數以及編譯線程數來動態調整。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6d/6d16d21410d7c9472dcbb0cb725818f2.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"即時編譯器爲什麼耗時","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在我們知道服務部署時CPU飆高是因爲觸發了即時編譯,而且即時編譯是在用戶程序運行的時候後臺執行的,可通過BackgroundCompilation參數設置,默認值是true,這個參數設置成false是很危險的操作,業務線程會等待即時編譯完成,可能一個世紀已經過去了。。。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那在後臺執行的過程中編譯器都做了什麼呢?大致分爲三個階段,流程如下。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2b/2b681f835ed2b53347d8dc72c012a86a.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一階段:一個平臺獨立的前端將字節碼構造成一種高級中間代碼表示(High-Level Intermediate Representation, 簡稱HIR),HIR使用靜態單分的形式來代表代碼值,這可以使得一些在HIR的構造之中和之後進行的優化動作更容易實現。在此之前編譯器會在字節碼上完成一部分基礎優化,比如方法內聯、常量傳播。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二階段:一個平臺相關的後端從HIR中產生低級中間代碼表示(Low-Level Intermediate Representation, 簡稱LIR),而在此之前會在HIR上完成另外一些優化,如空值檢查消除、範圍檢查消除等。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三階段:在平臺相關的後端使用線性掃描算法(Liner Scan Register Allocation)在LIR上分配寄存器,並在LIR上做窺孔優化,然後產生機器代碼。 以上階段是C1編譯器的大致過程,而C2編譯器會執行所有經典的優化動作,比如無用代碼消除、循環展開、逃逸分析等。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"怎麼解決問題","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"知道問題的根因,只需要對症下藥即可,我們最終的目標是服務在部署的時候不要影響上游的調用,所以要把即時編譯的執行時間和用戶程序運行時間避開,可以選擇在上游流量切入之前讓熱點代碼觸發即時編譯,也就是我們說的預熱,實現方式比較簡單,使用Spring提供的方法。對於整個服務大部分都是熱點代碼的,可以在JVM啓動腳本中加入“-XX:-TieredCompilation -server”參數來關閉分層編譯模式,啓用C2編譯器,然後在預熱的代碼中模擬調用一遍所有接口,這樣在流量接入之前會先進行即時編譯。需要注意並不是所有的代碼都會進行即時編譯,存儲機器碼的內存有限,過大的代碼即使達到一定次數也不會觸發。而我們大部分服務都符合“二八定律”,所以還是選擇保留分層編譯模式,在方法內部模擬調用熱門代碼,需要計算好分層模式下觸發即時編譯的閾值,也可以根據參數適當的調整閾值大小。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"文|Shane","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關注得物技術,攜手走向技術的雲端","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章