JVM 角度看代碼優化

從JVM角度看,有這幾種優化手段:java

  • 棧上分配:
    把對上分配對象空間的行爲轉化成棧上分配,減小YGC,提供性能
  • 同步省略
    同步代碼塊鎖消除
  • 標量替換
    爲棧上分配提供了基礎,和棧上分配時搭配作的

這幾個優化手段須要JVM配置以外,寫代碼時仍是須要配合的點,要不JVM優化也不會起做用,拜拜提供了優化手段而你不用面試

代碼優化點太多了,我覺淂仍是叉開來好些好理解一些......post


棧上分配

棧上分配具體內容看:JVM 面試題【中級】,我就再也不寫一遍了性能

方法內部變量寫的好一些,仔細一些以實現棧上分配的確有很大優點,這裏跑個例子測試

public class Max {

    public static void main(String[] args) {

        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 10000000; i++) {
            newValue();
        }

        long endTime = System.currentTimeMillis();
        System.out.println("Time:" + (endTime - startTime));

        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void newValue() {
        Dog dog = new Dog();
    }

}
複製代碼

關閉棧上分配

  • JVM配置:-Xms256m -Xmx256m -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
  • log 日誌:2次 YGC,耗時91毫秒,挺長了 w(゚Д゚)w
[GC (Allocation Failure) [PSYoungGen: 65536K->761K(76288K)] 65536K->769K(251392K), 0.0016895 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 66297K->713K(76288K)] 66305K->721K(251392K), 0.0023094 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Time:91
複製代碼
  • 內存快照:Dog 對象194萬個,好多,這仍是GC以後呀
  • GC快照:新生代64M裏吃了47M,這樣的短期內大量建立的對象,要是在生命週期長一點的話,直接會爆到老年代裏,估計會OOM,我這裏堆內存纔給了256M

啓動棧上分配

  • JVM配置:-Xms256m -Xmx256m -XX:+DoEscapeAnalysis -XX:+PrintGCDetails
  • log 日誌:沒有GC,耗時7毫秒,時間差距至關大呀 O(≧口≦)O
Time:7
複製代碼
  • 內存快照:Dog 對象只有7萬個了,固然實際老是和理論有些差距啊,理論上堆內存如今一個Dog對象都沒有才對的 ┑( ̄Д  ̄)┍
  • GC快照:新生代64M裏吃了25M,和上面差距仍是挺大的

總結

你們別看測試用例方法是執行1000萬次,就意味沒有實際意義啦,你們寫的程序,分分鐘你覺得方法執行的少嗎,能不能作到棧上分配堆性能是及其有意思的,差距你們都看到了吧優化

還沒完啊

神轉折來啦 (/// ̄皿 ̄)○~ 這是《深刻理解JVM虛擬機裏的原話》spa

逃逸分析的技術99年就出現了,一直到JDK1.6 Hotspot 纔開始支持初步的逃逸分析,即使到如今這項技術仍未成熟,還有很大的改進餘地。不成熟的緣由是逃逸分析的計算成本很是高,甚至不能保證帶來的性能優點會高於計算成本,在實際應用中,尤爲是大型應用中反而發現逃逸發分析可能出現不穩定的狀態。直到JDK7時才默認開啓這項技術,服務模式的java程序才支持3d

上面例子效果明顯,更多緣由是由於下面會說的標量替換,沒看到內存快照嘛,即使開啓逃逸分析以後,Dog對象在堆內存中仍是有很是多的對象存在,這和理論差距仍是滿大的日誌

同步省略

懶得打字了,你們看圖吧: code

典型的例子:

public void test() {
    Dog dog = new Dog();
    synchronized (dog) {
        System.out.println(dog);
    }
}
複製代碼

對於上面這個方法,JIT 動態編譯器雖然會幫咱們自動把鎖消除了,可是在這是在運行階段纔會優化的,編譯成字節碼時仍是能看到鎖指令的

另外雖然有JIT優化,可是相比咱們直接不寫鎖,優化以後的性能仍是不如的,你們最好仍是這樣寫,性能更好一些:

public void test() {
    Dog dog = new Dog();
    System.out.println(dog);
}
複製代碼

標量替換

java 中:

  • 標量: 是指一個沒法再被分解成更小的數據的數據,好比基礎數據類型
  • 聚合量: 相對的就是那些還能夠再分解的,好比對象就是
  • -XX:+EliminateAllocations JDK7開始默認是開啓的

在JIT階段,通過逃逸分析後,若是方法內對象符合棧上分配的規則,那會這個對象在棧上就會以標量的形式存儲,能夠進一步節省分配對象的操做

好比一個對象:

public class Dog extends Max {
    public int age = 10;
    public String name = "AA";
}
複製代碼

通過標量替換後,一個Dog對象會以這種方式存儲在局部變量表裏:

public void test() {
        // 一個 Dog 對象會變成下面這樣
        int age = 10;
        String name = "AA";
    }
複製代碼

標量替換爲棧上分配提供了很好的基礎 (⊙﹏⊙)

標量替換你要是在 JVM 配置裏面關了,那棧上分配就無論用了,標量替換、棧上分配這2個必須都得設置才能起做用

PS:想罵娘,深度越深的技術,只是點都是這樣拔出蘿蔔帶出泥,一茬接一茬,應接不暇,越看越暈,要是資料再補全,跳着來,尼瑪想死的心都有 (〃>目<)

相關文章
相關標籤/搜索