HotSwap和JRebel原理

HotSwap和JRebel原理服務器

HotSwap和Instrumentation

在2002年的時候,Sun在Java 1.4的JVM中引入了一種新的被稱做HotSwap的實驗性技術,這一技術被合成到了Debugger API內部,其容許調試者使用同一個類標識來更新類的字節碼。這意味着全部對象均可以引用一個更新後的類,並在它們的方法被調用的時候執行新的代碼,這就避免了不管什麼時候只要有類的字節碼被修改就要重載容器的這種要求。全部新式的IDE(包括Eclipse、IDEA和NetBeans)都支持這一技術,從Java 5開始,這一功能還經過Instrumentation API直接提供給Java應用使用。框架

hotswap

不幸的是,這種重定義僅限於修改方法體——除了方法體以外,它既不能添加方法或域,也不能修改其餘任何東西。這限制了HotSwap的實用性,且其還因其餘的一些問題而變得更糟:編輯器

Java編譯器經常會建立合成的方法或是域,儘管你僅是修改了一個方法體(好比說,在添加一個類字面常量(class literal)、匿名的和內部的類的時候等等)。 在調試模式下運行經常會下降應用的速度或是引入其餘的問題。分佈式

這些狀況致使了HotSwap不多被使用,較之應該可能被使用的頻度要低。工具

爲何HotSwap僅限於對方法體起做用?

自從引入了HotSwap以後,在最近的10年,這一問題已經被問了很是屢次。在支持作整組改變的JVM調用的bug中,這是一個得票率最高的bug ,但到目前爲止,這一問題一直沒有被落實。佈局

一個聲明:我不能說是一個JVM專家,我對JVM是如何實現的在整體上有着一個很好的理解,這幾年來我有和少數幾個(前)Sun工程師談過,不過我並無驗證我在這裏說的每一件事情。不過話雖如此,對於這個bug依然處開發狀態的緣由我確實是有一些想法的(不過若是你更清楚其中的緣由的話,歡迎指正)。性能

JVM是一種作了重度優化的軟件,運行在多個平臺上。性能和穩定性是其最高的優先事項。爲了在不一樣的環境中支持這些事項,Sun的JVM提供了這樣的功能特點:優化

兩個重度優化的即時編譯器(-client和-server)
 幾個多代(multi-generational )垃圾收集器

這些功能特性使得類模式(schema)的發展變成了一個至關大的挑戰。爲了理解這其中的緣由,咱們須要稍微靠近一點看一看,究竟是須要用什麼來支持方法和域的添加操做(甚至更深刻一些,修改繼承的層次結構)。spa

在被加載到JVM中時,對象是由內存中的結構來表示的,結構佔據了某個特定大小(它的域加上元數據)的連續的內存區域。爲了添加一個域,咱們須要調整結構的大小,但由於臨近的區域可能已被佔用,咱們就須要把整個結構從新分配到一個不一樣的區域中,這一區域中有足夠可用的空間來把它填寫進來。如今,因爲咱們其實是更新了一個類(並不只是某個對象),因此咱們不得不對該類的每個對象都作這樣的一件事。插件

這自己並不難實現——Java垃圾收集器就已是隨時都在作重分配對象的工做的了。問題是,一個「堆」的抽象就僅是一個抽象而已。內存的實際佈局取決於當前活動的垃圾收集器,並且,爲了能與全部這些對象兼容,重分配應該有可能會被委派給活動的垃圾收集器。JVM在重分配期間還須要掛起,所以其在此期間同時進行GC工做也是合理的。

添加一個方法並不要求更新對象的結構,但確實是須要更新類的結構的,這也會體如今堆上。不過考慮一下這種狀況:從類被載入以後的那一刻起,其從本質上來講就是被永久凍結了的。這使得JIT(Just-In-Time)可以完成JVM執行的主要優化操做——內聯。應用程序熱點中的大多數方法調用會被取消,這些代碼會被拷貝到對其作調用的方法中。一個簡單的檢測會被插進來,用以確保目標對象確實是咱們所認爲的對象。

因而就有了這樣好笑的事:在咱們可以添加方法到類中的時候,這種「簡單的檢查」是不夠的。咱們須要的是一個至關複雜的檢查,須要這樣更復雜的檢查來確保沒有使用了相同名字的方法被添加到目標類以及目標類的超類中。另外,咱們也能夠跟蹤全部的內聯點和它們的依賴,並在類被更新時,解除對它們所作的優化。兩種方式可選擇,或是付出性能方面的代價,或是帶來更高的複雜性。

最重要的是,考慮到咱們正在討論的是有着不一樣的內存模型和指令集的多個平臺,它們可能多多少少須要一些特定的處理,所以你給本身帶來的是一個代價太高而沒有太多投資回報的問題。

jrebel-agent

JRebel介紹

2007年,ZeroTurnaround宣佈提供一種被稱做JRebel(當時是JavaRebel)的工具,該工具能夠在無需動態類加載器的狀況下更新類,且只作極少的限制。不像HotSwap要依賴於IDE的集成,這一工具的工做方式是,監控磁盤上實際已編譯的.class文件,不管什麼時候只要有文件被更新就更新類。這意味着若是願意的話,你能夠把JRebel和文本編輯器、命令行的編譯器放在一塊兒使用。固然,它也被巧妙地整合到了Eclipse、InteliJ和NetBeans中。與動態的類加載器不同,JRebel保留了全部現有的對象和類的標識和狀態,容許開發者繼續使用他們的應用而不會產生延遲。

如何使之生效?

對於初學者來講,JRebel工做在與HotSwap不一樣的一個抽象層面上。鑑於HotSwap是工做在虛擬機層面上,且依賴於JVM的內部運做,JRebel用到了JVM的兩個顯著的功能特徵——抽象的字節碼和類加載器。類加載器容許JRebel辨別出類被加載的時刻,而後實時地翻譯字節碼,用以在虛擬機和可執行代碼之間建立另外一個抽象層。

也有人使用這一功能特性來提供分析器、性能監控、後續(continuation)、軟件事務性內存以及甚至是分佈式的堆。 把字節碼抽象和類加載器結合在一塊兒,這是一種強大的組合,可被用來實現各類比類重載還要不尋常的功能。當咱們越是深刻地研究這一問題,咱們就會看到面臨的挑戰並不只是在類重載這件事上,並且是還要在性能和兼容性方面沒有明顯退化的狀況下來作這件事情,

正如咱們在Reloading Java Classes 101 一文中所作的回顧同樣,重載類存在的問題是,一旦類被載入,它就不能被卸載或是改變;可是隻要咱們願意,咱們就能夠自由地加載新的類。爲了理解在理論上咱們是如何重載類的,讓咱們來研究一下Java平臺上的動態語言。具體來講,讓咱們先來看一看JRudy(咱們作了許多的簡化,以避免對任何重要人物形成折磨)。

儘管JRuby以「類(class)」做爲其功能特性,但在運行時,其每一個對象都是動態的,任什麼時候候均可以加入新的域和方法。這意味着JRuby對象與Map沒有什麼兩樣,有着從方法名字到方法實現的映射,以及域名到其值的映射。這些方法的實現被包含在匿名的類中,在遇到方法時這些類就會被生成。若是你添加了一個方法,則全部JRuby要作的事情就是生成一個新的匿名類,該類包含了這一方法的方法體。由於每一個匿名類都有一個惟一的名稱,所以在加載該類是不會有問題的,而這樣作的結果是,應用被實時動態地更新了。

從理論上來講,因爲字節碼翻譯一般是用來修改類的字節碼,所以若僅僅是爲了根據須要建立足夠多的類來履行類的功能的話,咱們沒有什麼理由不能使用類中的信息。這樣的話,咱們就可使用如JRuby所作的相同轉換來把全部的Java類分割成持有者類和方法體類。不幸的是,這樣的一種作法會遭受(至少是)以下的問題:

性能。這樣的設置將意味着,每一個方法調用都會遭遇重定向。咱們能夠作優化,但應用程序的速度將會變慢至少一個數量級,內存的使用也會扶搖直上,由於有這麼多的類被建立。 Java的SDK類。Java SDK中的類明顯地比應用或是庫中的類更加難以處理。此外它們一般會以本地的代碼來實現,所以不能以「JRuby」的方式作轉換。然而,若是咱們讓它們保持原樣的話,那麼就會引起各類的不兼容性錯誤,這些錯誤有多是沒法繞開的。 兼容性。儘管Java是一種靜態的語言,可是它包含了一些動態的特性,好比說反射和動態代理等。若是咱們採用了「JRuby」式的轉換的話,這些功能特性就會失效,除非咱們使用本身的類來替換掉Reflection API,而這些類知道這些要作的轉換。

所以,JRebel並無採用這樣的作法。相反,其使用了一種更復雜的方法,基於先進的編譯技術,留給咱們一個主類和幾個匿名的支持類,這些類由JIT的轉換運行時作支持,其容許所進行的修改不會帶來任何明顯的性能或是兼容性的退化。它還

留有儘量多完整的方法調用,這意味着JRebel把性能開銷下降到了最小,使其輕量級化。
避免了改編(instrument)Java SDK,除了少數幾個須要保持兼容性的地方外。
調整Reflection API的結果,這樣咱們就可以把這些結果中已添加/已刪除的成員正確地包含進來。這也意味着註解(Annotation)的改變對於應用來講是可見的。

除了類重載以外——還有歸檔文件

重載類是一件Java開發者已經抱怨了好久的事情,不過一旦咱們解決了它以後,另外的一些問題就隨之而來了。

Java EE標準的制定並未怎麼關注開發的週轉期(Turnaround)(指的是從對代碼作修改到觀察到改變在應用中形成的影響這一過程所花費的時間)。其設想的是,全部的應用和它們的模塊都被打包到歸檔文件(JAR、WAR和EAR)中,這意味着在可以更新應用中的任何文件以前,你須要更新歸檔文件——這一般是一個代價高昂的操做,涉及了諸如Ant或是Maven這一類的構建系統。正如咱們在Reloading Java Classes 301 所作的討論那樣,能夠經過使用展開式的開發和增量的IDE構建來儘可能減小花銷,不過對於大型的應用來講,這種作法一般不是一個可行的選擇。

爲了解決這一問題,在JRebel 2.x中,咱們爲用戶開發了一種方式來把歸檔的應用和模塊映射回到工做區中——用戶在每一個應用和模塊中建立一個rebel.xml配置文件,該文件告訴JRebel在哪裏能夠找到源文件。JRebel與應用服務器整合在一塊兒,當某個類或是資源被更新時,其被從工做區中而不是從歸檔文件中讀入。

workspace-map

這一作法不只容許類的即時更新,且容許諸如HTML、XML、JSP、CSS、.properties等之類的任何類型的資源的即時更新。Maven用戶甚至不須要建立一個rebel.xml文件,由於Maven插件會自動地生成該文件。

除了類重載以外——還有配置和元數據

在消除週轉期的這一過程當中,另外一個問題變得明顯起來:現現在的應用已不只僅是類和資源,它們還經過大量的配置和元數據綁定在一塊兒。當配置發生改變時,改變應該被反映到那個正在運行的應用上。然而,僅把對配置文件的修改變成是可見的是不夠的,具體的框架必需要要重載配置,把改變反映到應用中才行。

conf

爲了在JRebel中支持這些類型的改變,咱們開發了一個開源的API ,該API容許咱們的團隊和第三方的捐獻者使用框架特有的插件來使用JRebel的功能特性,把配置中所作的改變傳播到框架中。例如,咱們支持動態實時地在Spring中添加bean和依賴,以及支持在其餘框架中所作的各類各樣的改變。

結論

本文總結了在未使用動態類加載器狀況下的各類重載Java類的方法。咱們還討論了致使HotSwap侷限性的緣由,揭示了JRebel幕後的工做方式,以及討論了在解決類重載問題時出現的其餘問題。

原文地址:http://article.yeeyan.org/view/213582/186226

相關文章
相關標籤/搜索