自我修复的JVM
這篇帖子是關(guān)于一個(gè)應(yīng)用程序的示例,其中解決每個(gè)IT問(wèn)題的第一個(gè)解決方案(“您嘗試過(guò)關(guān)閉并重新打開(kāi)它”)可能適得其反,弊大于利。
我們沒(méi)有關(guān)閉或重新打開(kāi)設(shè)備的方法,而是擁有一個(gè)可以自愈的應(yīng)用程序:它在一開(kāi)始就失敗了,但過(guò)了一段時(shí)間便開(kāi)始平穩(wěn)運(yùn)行。 為了舉例說(shuō)明這種應(yīng)用的實(shí)際應(yīng)用,我們以最簡(jiǎn)單的形式重新創(chuàng)建了該應(yīng)用, 并從Heinz Kabutz的Java Newsletter已有5年歷史的帖子中汲取了靈感 :
package eu.plumbr.test;public class HealMe {private static final int SIZE = (int) (Runtime.getRuntime().maxMemory() * 0.6);public static void main(String[] args) throws Exception {for (int i = 0; i < 1000; i++) {allocateMemory(i);}}private static void allocateMemory(int i) {try {{byte[] bytes = new byte[SIZE];System.out.println(bytes.length);}byte[] moreBytes = new byte[SIZE];System.out.println(moreBytes.length);System.out.println("I allocated memory successfully " + i);} catch (OutOfMemoryError e) {System.out.println("I failed to allocate memory " + i);}} }上面的代碼在一個(gè)循環(huán)中分配兩個(gè)大塊內(nèi)存。 這些分配中的每一個(gè)都等于總可用堆大小的60%。 由于分配是在同一方法中按順序進(jìn)行的,因此人們可能希望此代碼不斷拋出java.lang.OutOfMemoryError:Java堆空間錯(cuò)誤,并且永遠(yuǎn)不會(huì)成功完成allocateMemory()方法。
因此,讓我們從對(duì)源代碼的靜態(tài)分析開(kāi)始,看看我們的期望是否正確:
在這里我們看到,在偏移量3-5上,第一個(gè)數(shù)組被分配并存儲(chǔ)到索引為1的局部變量中。然后,在偏移量17上,另一個(gè)數(shù)組將被分配。 但是第一個(gè)數(shù)組仍由局部變量引用,因此第二個(gè)分配應(yīng)始終因OOM而失敗。 字節(jié)碼解釋器只是不能讓GC清理第一個(gè)數(shù)組,因?yàn)樗匀槐粡?qiáng)烈引用。
我們的靜態(tài)代碼分析向我們表明,由于兩個(gè)根本原因,所提供的代碼不應(yīng)成功運(yùn)行,而在一種情況下,應(yīng)該可以成功運(yùn)行。 這三者中哪一個(gè)是正確的? 讓我們實(shí)際運(yùn)行它,自己看看。 事實(shí)證明,這兩個(gè)結(jié)論都是正確的。 首先,應(yīng)用程序無(wú)法分配內(nèi)存。 但是一段時(shí)間后(在我的Java 8的Mac OS X上,它發(fā)生在第255次迭代中),分配開(kāi)始成功:
java -Xmx2g eu.plumbr.test.HealMe 1145359564 I failed to allocate memory 0 1145359564 I failed to allocate memory 1… cut for brevity ...I failed to allocate memory 254 1145359564 I failed to allocate memory 255 1145359564 1145359564 I allocated memory successfully 256 1145359564 1145359564 I allocated memory successfully 257 1145359564 1145359564 Self-healing code is a reality! Skynet is near...為了了解實(shí)際發(fā)生的事情,我們需要思考一下,程序執(zhí)行期間會(huì)發(fā)生什么變化? 當(dāng)然,顯而易見(jiàn)的答案是可以進(jìn)行即時(shí)編譯。 您還記得嗎,即時(shí)編譯是JVM的一種內(nèi)置機(jī)制,可以?xún)?yōu)化代碼熱點(diǎn)。 為此,JIT監(jiān)視正在運(yùn)行的代碼,并且在檢測(cè)到熱點(diǎn)時(shí),JIT會(huì)將您的字節(jié)碼編譯為本機(jī)代碼,在過(guò)程中執(zhí)行不同的優(yōu)化,例如方法內(nèi)聯(lián)和消除無(wú)效代碼。
通過(guò)打開(kāi)以下命令行選項(xiàng)并重新啟動(dòng)程序,看看是否是這種情況:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+LogCompilation這將生成一個(gè)日志文件,在本例中為hotspot_pid38139.log,其中38139是Java進(jìn)程的PID。 在此文件中,可以找到以下行:
<task_queued compile_id='94' method='HealMe allocateMemory (I)V' bytes='83' count='256' iicount='256' level='3' stamp='112.305' comment='tiered' hot_count='256'/>這意味著,在執(zhí)行256次allocateMemory()方法后,C1編譯器已決定將該方法排隊(duì)以進(jìn)行C1層3編譯。 您可以在此處獲得有關(guān)分層編譯級(jí)別和不同閾值的更多信息。 因此,我們的前256次迭代是在解釋模式下運(yùn)行的,在該模式下,字節(jié)碼解釋器(作為簡(jiǎn)單的堆棧計(jì)算機(jī))無(wú)法預(yù)先知道是否會(huì)繼續(xù)使用某些變量(在這種情況下為字節(jié))。 但是JIT可以立即看到整個(gè)方法,因此可以推斷出不再使用字節(jié),并且實(shí)際上可以使用GC。 因此,垃圾收集最終可以發(fā)生,并且我們的程序神奇地自我修復(fù)了。 現(xiàn)在,我只希望沒(méi)有讀者真正負(fù)責(zé)在生產(chǎn)中調(diào)試這種情況。 但是,如果您希望使某人的生活陷入困境,那么將這樣的代碼引入生產(chǎn)環(huán)境將是實(shí)現(xiàn)此目標(biāo)的肯定方法。
翻譯自: https://www.javacodegeeks.com/2014/12/self-healing-jvm.html
總結(jié)
- 上一篇: 哪两种颜色可调成紫色 紫色怎么调
- 下一篇: JPA实体图