自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

逃逸分析在 Java 中的應(yīng)用與優(yōu)化

開(kāi)發(fā)
逃逸分析技術(shù)是JVM用于提高性能以及節(jié)省內(nèi)存的手段,在JVM編譯語(yǔ)境下也就是我們常說(shuō)的JIT階段,。

逃逸分析技術(shù)算是在JVM面試題偶有提及的一個(gè)考察點(diǎn),當(dāng)然如果你能夠講解JVM工作原理的時(shí)候提及這一點(diǎn),這一定會(huì)增加面試官對(duì)你的好感,通過(guò)對(duì)本篇文章的閱讀,你將能夠從容的解決以下幾個(gè)面試題:

  • 什么是逃逸分析技術(shù)?
  • 逃逸分析技術(shù)解決什么問(wèn)題?帶來(lái)什么好處?
  • 如何更好的理解或者運(yùn)用逃逸分析技術(shù)?

什么是逃逸分析

逃逸分析技術(shù)是JVM用于提高性能以及節(jié)省內(nèi)存的手段,在JVM編譯語(yǔ)境下也就是我們常說(shuō)的JIT階段,關(guān)于逃逸分析的概念,引用《深入理解Java虛擬機(jī)》的說(shuō)法:

逃逸分析的基本原理是:分析對(duì)象動(dòng)態(tài)作用域,當(dāng)一個(gè)對(duì)象在方法里面被定義后,它可能被外部方法所引用,例如作為調(diào)用參數(shù)傳遞到其他方法中,這種稱為方法逃逸;甚至還有可能被外部線程訪問(wèn)到,譬如賦值給可以在其他線程中訪問(wèn)的實(shí)例變量,這種稱為線程逃逸;從不逃逸、方法逃逸到線程逃逸,稱為對(duì)象由低到高的不同逃逸程度。

只要編譯階段判定當(dāng)前對(duì)象并沒(méi)有發(fā)生逃逸,那么它就會(huì)采用棧上分配、標(biāo)量替換、同步鎖消除等手段提升程序執(zhí)行性能和節(jié)省內(nèi)存開(kāi)銷(xiāo),具體場(chǎng)景還得查看是發(fā)生方法逃逸還是線程逃逸。

那么我們又該如何判斷對(duì)象是否逃逸呢?我們不妨基于上述的判斷條件來(lái)看看這個(gè)示例,假設(shè)我們現(xiàn)在有一個(gè)user類(lèi),我們通過(guò)UserService進(jìn)行初始化,那么請(qǐng)問(wèn)這段代碼是否發(fā)生逃逸呢?

public class UserService {

    private User user;

    public void init() {
        user = new User();
        user.setId(RandomUtil.randomInt(10));
        user.setName(RandomUtil.randomString(3));
    }
}

答案當(dāng)然是肯定的,因?yàn)檫@段代碼方法內(nèi)所創(chuàng)建的對(duì)象被外部的main函數(shù)所引用,也就是我們所說(shuō)的方法逃逸。

再來(lái)看看這段代碼,典型的在方法內(nèi)創(chuàng)建然后被外部函數(shù)所引用,也就是所謂的方法逃逸:

public User createUser() {
        User user = new User();
        user.setId(RandomUtil.randomInt(10));
        user.setName(RandomUtil.randomString(3));
        return user;
    }

而這段stringBuffer 已經(jīng)被其他線程實(shí)例所訪問(wèn)到,也就是典型的線程逃逸:

public static void main(String[] args) throws InterruptedException {
        StringBuffer stringBuffer = new StringBuffer();
        CountDownLatch countDownLatch = new CountDownLatch(3);
        //調(diào)用appendStr操作stringBuffer
        new Thread(() -> {
            appendStr(stringBuffer);
            countDownLatch.countDown();
        }).start();

        //循環(huán)拼接操作stringBuffer
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                stringBuffer.append("aaa");
            }
            countDownLatch.countDown();
        }).start();
        
        //循環(huán)拼接操作stringBuffer
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                stringBuffer.append("aaa");
            }
            countDownLatch.countDown();
        }).start();

        countDownLatch.await();
        System.out.println(stringBuffer);
    }

如何運(yùn)用到逃逸分析技術(shù)

1.棧上分配(針對(duì)未逃逸或方法逃逸)

下面這段代碼僅在方法內(nèi)部完成對(duì)象創(chuàng)建或者打印,其對(duì)象并沒(méi)有被外部方法所引用和暴露,對(duì)象就沒(méi)有發(fā)生逃逸,對(duì)于沒(méi)有發(fā)生逃逸的代碼或者上文中方法逃逸的代碼端,JIT會(huì)通過(guò)棧上分配減少內(nèi)存占用和GC壓力。

 Map<Integer, User> userMap = new HashMap<>();


    public int getUserAgeById(int id) {
       User user = new User();
        user.setId(RandomUtil.randomInt(10));
        user.setName(RandomUtil.randomString(3));
        //打印用戶信息
        printUserInfo(user);
    }

2.分離對(duì)象或標(biāo)量替換(針對(duì)未逃逸)

如果僅僅是操作未逃逸對(duì)象的某些簡(jiǎn)單運(yùn)算,我們同樣可以只在棧幀內(nèi)使用這個(gè)對(duì)象,如此JVM就會(huì)將這個(gè)對(duì)象打散,將對(duì)象打散為無(wú)數(shù)個(gè)小的局部變量,實(shí)現(xiàn)標(biāo)量替換。

如下所示,這段代碼沒(méi)有發(fā)生任何逃逸,JVM會(huì)避免創(chuàng)建Point ,而是通過(guò)棧上創(chuàng)建基本變量完成邏輯操作:

public static void main(String args[]) {
    alloc();
}
class Point {
    private int x;
    private int y;
}
private static void alloc() {
    Point point = new Point(1,2);
    System.out.println("point.x" + point.x + ";point.y" + point.y);
}

進(jìn)而直接標(biāo)量替換,直接在棧上分配x和y的值,完成輸出打印。

private static void alloc() {
    int x = 1;
    int y = 2;
    System.out.println("point.x = " + x + "; point.y=" + y);
}

3.同步鎖消除(針對(duì)未逃逸線程)

這一點(diǎn)就比較有趣了,我們都知道使用StringBuffer可以保證線程安全,因?yàn)槠洳僮骱瘮?shù)都有帶synchronized關(guān)鍵字,那么請(qǐng)問(wèn)這段代碼會(huì)上鎖嗎?

public void appendStr(int count) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < count; i++) {
            sb.append("no: " + i + " ");
        }
    }

答案是不會(huì),因?yàn)槲覀儺?dāng)前操作的StringBuffer 對(duì)象并沒(méi)有發(fā)生線程逃逸,它僅僅在函數(shù)內(nèi)部進(jìn)行字符串操作,所以針對(duì)appendStr內(nèi)部邏輯,其直接將其優(yōu)化為StringBuilder:

4.線程逃逸分析的更進(jìn)一步

請(qǐng)問(wèn)實(shí)例方法調(diào)用靜態(tài)方法,StringBuffer作為變量傳入,是否發(fā)生逃逸,直接創(chuàng)建一個(gè)main方法調(diào)用這段代碼,方法是否發(fā)生逃逸?

public void appendStr(int count) {
        StringBuffer sb = new StringBuffer();
        loop(count, sb);
    }

    private static void loop(int count, StringBuffer sb) {
        for (int i = 0; i < count; i++) {
            sb.append("no: " + i + " ");
        }
    }

答案是發(fā)生了方法逃逸,但是沒(méi)有發(fā)生線程逃逸,但我們的代碼是單線程執(zhí)行這段代碼,即使StringBuffer 由外部傳入,函數(shù)內(nèi)部依然可以進(jìn)行鎖消除將其內(nèi)部的拼接邏輯用StringBuilder進(jìn)行字符串拼接:

再來(lái)看看這段代碼,請(qǐng)問(wèn)發(fā)生逃逸了嗎?

 public void appendStr(int count) {
        StringBuffer sb = new StringBuffer();
        loop(count, sb);
    }

    private static String loop(int count, StringBuffer sb) {
        for (int i = 0; i < count; i++) {
            sb.append("no: " + i + " ");
        }
        return sb.toString();
    }

答案是沒(méi)有發(fā)生線程逃逸,返回的字符串還是沒(méi)有被外部線程操作,所以最終還是被轉(zhuǎn)為StringBuilder:

而下面這段代碼就是典型的逃逸,可以看到多線程場(chǎng)景下StringBuffer 被多線程共享和訪問(wèn),此時(shí)JIT優(yōu)化就會(huì)視為對(duì)象逃逸:

public static void main(String[] args) throws InterruptedException {
        StringBuffer stringBuffer = new StringBuffer();
        CountDownLatch countDownLatch = new CountDownLatch(3);
        //調(diào)用appendStr操作stringBuffer
        new Thread(() -> {
            appendStr(stringBuffer);
            countDownLatch.countDown();
        }).start();

        //循環(huán)拼接操作stringBuffer
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                stringBuffer.append("aaa");
            }
            countDownLatch.countDown();
        }).start();

        //循環(huán)拼接操作stringBuffer
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                stringBuffer.append("aaa");
            }
            countDownLatch.countDown();
        }).start();

        countDownLatch.await();
        System.out.println(stringBuffer);
    }

    public static void appendStr(StringBuffer stringBuffer) {
        for (int i = 0; i < 10; i++) {
            stringBuffer.append(i);
        }

    }

所以appendStr在判定線程逃逸之后,并沒(méi)有將StringBuffer變?yōu)镾tringBuilder:

小結(jié)

合理的在棧幀上解決問(wèn)題可以避免對(duì)象逃逸,從而讓JIT盡可能的去進(jìn)行優(yōu)化,這一點(diǎn)我想應(yīng)該是一個(gè)Java程序員對(duì)于代碼的極致追求了。

責(zé)任編輯:趙寧寧 來(lái)源: 寫(xiě)代碼的SharkChili
相關(guān)推薦

2018-07-09 15:11:14

Java逃逸JVM

2020-07-21 14:19:18

JVM編程語(yǔ)言

2024-04-07 11:33:02

Go逃逸分析

2024-07-23 08:06:19

緩存技術(shù)策略

2022-05-10 11:23:56

漏洞補(bǔ)洞過(guò)程入侵檢測(cè)

2020-05-13 15:10:04

矩陣乘法深度學(xué)習(xí)人工智能-

2010-10-11 09:28:07

2023-04-25 08:01:23

JavaQuarkusKubernetes

2024-03-04 08:00:00

Java開(kāi)發(fā)

2011-06-20 15:55:14

SEO

2021-10-14 10:22:19

逃逸JVM性能

2010-02-23 10:25:29

2011-01-21 10:01:07

jQueryjavascriptweb

2012-03-27 14:04:54

JavaEnum

2020-08-14 10:00:34

Node前端應(yīng)用

2009-03-03 09:56:00

協(xié)議分析器WLAN

2010-09-02 09:15:33

協(xié)議分析器Wi-Fi

2022-07-14 14:46:51

數(shù)據(jù)庫(kù)SQL系統(tǒng)設(shè)計(jì)

2023-01-10 09:18:37

Go內(nèi)存分配逃逸

2022-11-30 08:19:15

內(nèi)存分配Go逃逸分析
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)