時間:2023-06-29 06:18:01 | 來源:網(wǎng)站運(yùn)營
時間:2023-06-29 06:18:01 來源:網(wǎng)站運(yùn)營
JVM系列之:關(guān)于逃逸分析的學(xué)習(xí):上文講解完方法內(nèi)聯(lián)后,JIT 即時編譯還有一個最前沿的優(yōu)化技術(shù):逃逸分析(Escape Analysis) 。廢話少說,我們直接步入正題吧。//StringBuffer對象發(fā)生了方法逃逸public static StringBuffer createStringBuffer(String s1, String s2) { StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); return sb; } public static String createString(String s1, String s2) { StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); return sb.toString(); }
關(guān)于逃逸分析技術(shù),本人想過用代碼展示對象是否發(fā)生了逃逸,比如說上述代碼,根據(jù)理論知識可以認(rèn)為 createStringBuffer 方法中發(fā)生了逃逸,但是具體是個什么情況,咱們都不清楚。雖然 JVM 有個參數(shù) PrintEscapeAnalysis 可以顯示分析結(jié)果,但是該參數(shù)僅限于 debug 版本的 JDK 才可以進(jìn)行調(diào)試,多次嘗試后,未能編譯出 debug 版本的 JDK,暫且沒什么思路,所以查看逃逸分析結(jié)果這件事先往后放一放,后續(xù)學(xué)習(xí) JVM 調(diào)優(yōu)再進(jìn)一步來學(xué)習(xí)。-XX:+EliminateLocks
(默認(rèn)開啟)可以開啟同步消除。 這個取消同步的過程就叫同步消除,也叫鎖消除。@Getterpublic class Worker { private String name; private double money; public Worker() { } public Worker(String name) { this.name = name; } public void makeMoney() { money++; }}
測試代碼如下:public class SynchronizedTest { public static void work(Worker worker) { worker.makeMoney(); } public static void main(String[] args) throws InterruptedException { long start = System.currentTimeMillis(); Worker worker = new Worker("hresh"); new Thread(() -> { for (int i = 0; i < 20000; i++) { work(worker); } }, "A").start(); new Thread(() -> { for (int i = 0; i < 20000; i++) { work(worker); } }, "B").start(); long end = System.currentTimeMillis(); System.out.println(end - start); Thread.sleep(100); System.out.println(worker.getName() + "總共賺了" + worker.getMoney()); }}
執(zhí)行結(jié)果如下:52hresh總共賺了28224.0
可以看出,上述兩個線程同時修改同一個 Worker 對象的 money 數(shù)據(jù),對于 money 字段的讀寫發(fā)生了競爭,導(dǎo)致最后結(jié)果不正確。像上述這種情況,即時編譯器經(jīng)過逃逸分析后認(rèn)定對象發(fā)生了逃逸,那么肯定不能進(jìn)行同步消除優(yōu)化。//JVM參數(shù):-Xms60M -Xmx60M -XX:+PrintGCDetails -XX:+PrintGCDateStampspublic class SynchronizedTest { public static void lockTest() { Worker worker = new Worker(); synchronized (worker) { worker.makeMoney(); } } public static void main(String[] args) throws InterruptedException { long start = System.currentTimeMillis(); new Thread(() -> { for (int i = 0; i < 500000; i++) { lockTest(); } }, "A").start(); new Thread(() -> { for (int i = 0; i < 500000; i++) { lockTest(); } }, "B").start(); long end = System.currentTimeMillis(); System.out.println(end - start); }}
輸出結(jié)果如下:56Heap PSYoungGen total 17920K, used 9554K [0x00000007bec00000, 0x00000007c0000000, 0x00000007c0000000) eden space 15360K, 62% used [0x00000007bec00000,0x00000007bf5548a8,0x00000007bfb00000) from space 2560K, 0% used [0x00000007bfd80000,0x00000007bfd80000,0x00000007c0000000) to space 2560K, 0% used [0x00000007bfb00000,0x00000007bfb00000,0x00000007bfd80000) ParOldGen total 40960K, used 0K [0x00000007bc400000, 0x00000007bec00000, 0x00000007bec00000) object space 40960K, 0% used [0x00000007bc400000,0x00000007bc400000,0x00000007bec00000) Metaspace used 4157K, capacity 4720K, committed 4992K, reserved 1056768K class space used 467K, capacity 534K, committed 640K, reserved 1048576K
在 lockTest 方法中針對新建的 Worker 對象加鎖,并沒有實際意義,經(jīng)過逃逸分析后認(rèn)定對象未逃逸,則會進(jìn)行同步消除優(yōu)化。JDK8 默認(rèn)開啟逃逸分析,我們嘗試關(guān)閉它,再看看輸出結(jié)果。-Xms60M -Xmx60M -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+PrintGCDateStamps
輸出結(jié)果變?yōu)椋?br>732022-03-01T14:51:08.825-0800: [GC (Allocation Failure) [PSYoungGen: 15360K->1439K(17920K)] 15360K->1447K(58880K), 0.0018940 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] Heap PSYoungGen total 17920K, used 16340K [0x00000007bec00000, 0x00000007c0000000, 0x00000007c0000000) eden space 15360K, 97% used [0x00000007bec00000,0x00000007bfa8d210,0x00000007bfb00000) from space 2560K, 56% used [0x00000007bfb00000,0x00000007bfc67f00,0x00000007bfd80000) to space 2560K, 0% used [0x00000007bfd80000,0x00000007bfd80000,0x00000007c0000000) ParOldGen total 40960K, used 8K [0x00000007bc400000, 0x00000007bec00000, 0x00000007bec00000) object space 40960K, 0% used [0x00000007bc400000,0x00000007bc402000,0x00000007bec00000) Metaspace used 4153K, capacity 4688K, committed 4864K, reserved 1056768K class space used 466K, capacity 502K, committed 512K, reserved 1048576K
經(jīng)過對比發(fā)現(xiàn),關(guān)閉逃逸分析后,執(zhí)行時間變長,且內(nèi)存占用變大,同時發(fā)生了垃圾回收。public class ScalarTest { public static double getMoney() { Worker worker = new Worker(); worker.setMoney(100.0); return worker.getMoney() + 20; } public static void main(String[] args) { getMoney(); }}
經(jīng)過逃逸分析,Worker 對象未逃逸出 getMoney()的調(diào)用,因此可以對聚合量 worker 進(jìn)行分解,得到局部變量 money,進(jìn)行標(biāo)量替換后的偽代碼:public class ScalarTest { public static double getMoney() { double money = 100.0; return money + 20; } public static void main(String[] args) { getMoney(); }}
對象拆分后,對象的成員變量改為方法的局部變量,這些字段既可以存儲在棧上,也可以直接存儲在寄存器中。標(biāo)量替換因為不必創(chuàng)建對象,減輕了垃圾回收的壓力。-XX:+EliminateAllocations
可以開啟標(biāo)量替換(默認(rèn)是開啟的), -XX:+PrintEliminateAllocations
(同樣需要debug版本的JDK)查看標(biāo)量替換情況。public static void bar(boolean cond) { Object foo = new Object(); if (cond) { foo.hashCode(); }}// 可以手工優(yōu)化為:public static void bar(boolean cond) { if (cond) { Object foo = new Object(); foo.hashCode(); }}
假設(shè) if 語句的條件成立的可能性只有 1%,那么在 99% 的情況下,程序沒有必要新建對象。其手工優(yōu)化的版本正是部分逃逸分析想要自動達(dá)到的成果。public class PartialEscapeTest { long placeHolder0; long placeHolder1; long placeHolder2; long placeHolder3; long placeHolder4; long placeHolder5; long placeHolder6; long placeHolder7; long placeHolder8; long placeHolder9; long placeHoldera; long placeHolderb; long placeHolderc; long placeHolderd; long placeHoldere; long placeHolderf; public static void foo(boolean flag) { PartialEscapeTest o = new PartialEscapeTest(); if (flag) { o.hashCode(); } } public static void main(String[] args) { for (int i = 0; i < 1000000; i++) { foo(false); } }}
本次測試選用的是 JDK11,開啟 Graal 編譯器需要配置如下參數(shù):-XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler
分別輸出使用 C2 編譯器或 Graal 編譯器的 GC 日志,對應(yīng)命令為:java -Xlog:gc* PartialEscapeTestjava -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler -Xlog:gc* PartialEscapeTest
通過對比 GC 日志可以發(fā)現(xiàn)內(nèi)存占用情況不一致,Graal 編譯器下內(nèi)存占用更小一點(diǎn)。[0.012s][info][gc,heap] Heap region size: 1M[0.017s][info][gc ] Using G1[0.017s][info][gc,heap,coops] Heap address: 0x0000000700000000, size: 4096 MB, Compressed Oops mode: Zero based, Oop shift amount: 3[0.345s][info][gc,heap,exit ] Heap[0.345s][info][gc,heap,exit ] garbage-first heap total 262144K, used 21504K [0x0000000700000000, 0x0000000800000000)[0.345s][info][gc,heap,exit ] region size 1024K, 18 young (18432K), 0 survivors (0K)[0.345s][info][gc,heap,exit ] Metaspace used 6391K, capacity 6449K, committed 6784K, reserved 1056768K[0.345s][info][gc,heap,exit ] class space used 552K, capacity 571K, committed 640K, reserved 1048576K
Graal[0.019s][info][gc,heap] Heap region size: 1M[0.025s][info][gc ] Using G1[0.025s][info][gc,heap,coops] Heap address: 0x0000000700000000, size: 4096 MB, Compressed Oops mode: Zero based, Oop shift amount: 3[0.611s][info][gc,start ] GC(0) Pause Young (Normal) (G1 Evacuation Pause)[0.612s][info][gc,task ] GC(0) Using 6 workers of 10 for evacuation[0.615s][info][gc,phases ] GC(0) Pre Evacuate Collection Set: 0.0ms[0.615s][info][gc,phases ] GC(0) Evacuate Collection Set: 3.1ms[0.615s][info][gc,phases ] GC(0) Post Evacuate Collection Set: 0.2ms[0.615s][info][gc,phases ] GC(0) Other: 0.6ms[0.615s][info][gc,heap ] GC(0) Eden regions: 24->0(150)[0.615s][info][gc,heap ] GC(0) Survivor regions: 0->3(3)[0.615s][info][gc,heap ] GC(0) Old regions: 0->4[0.615s][info][gc,heap ] GC(0) Humongous regions: 5->5[0.615s][info][gc,metaspace ] GC(0) Metaspace: 8327K->8327K(1056768K)[0.615s][info][gc ] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 29M->11M(256M) 3.941ms[0.615s][info][gc,cpu ] GC(0) User=0.01s Sys=0.01s Real=0.00sCannot use JVMCI compiler: No JVMCI compiler found[0.616s][info][gc,heap,exit ] Heap[0.616s][info][gc,heap,exit ] garbage-first heap total 262144K, used 17234K [0x0000000700000000, 0x0000000800000000)[0.616s][info][gc,heap,exit ] region size 1024K, 9 young (9216K), 3 survivors (3072K)[0.616s][info][gc,heap,exit ] Metaspace used 8336K, capacity 8498K, committed 8832K, reserved 1056768K[0.616s][info][gc,heap,exit ] class space used 768K, capacity 802K, committed 896K, reserved 1048576K
查看 Graal 在 JDK11 上的編譯結(jié)果,可以執(zhí)行下述命令:java -XX:+PrintCompilation -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler -cp /Users/xxx/IdeaProjects/java_deep_learning/src/main/java/com/msdn/java/javac/escape ScalarTest > out-jvmci.txt
關(guān)鍵詞:分析,學(xué)習(xí),系列,逃逸
客戶&案例
微信公眾號
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。