亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java8 Lambda和Invokedynamic詳情

 更新時(shí)間:2021年09月22日 09:13:21   作者:sofia  
關(guān)于Java8的Lambda 我們可以將lambda表達(dá)式與新的Streams API結(jié)合起來(lái),以表達(dá)豐富的數(shù)據(jù)處理查詢(xún),下面文章小編就列舉簡(jiǎn)單的例子給大家介說(shuō)吧,感興趣的小伙伴可以參考下面文章的具體內(nèi)容奧

一、闡明lambda

Java8于2014年3月發(fā)布,并引入了lambda表達(dá)式作為其旗艦功能。我們可能已經(jīng)在代碼庫(kù)中使用它們來(lái)編寫(xiě)更簡(jiǎn)潔、更靈活的代碼。例如,我們可以將lambda表達(dá)式與新的Streams API結(jié)合起來(lái),以表達(dá)豐富的數(shù)據(jù)處理查詢(xún):

int total = invoices.stream()
                    .filter(inv -> inv.getMonth() == Month.JULY)
                    .mapToInt(Invoice::getAmount)
                    .sum();

此示例顯示如何從發(fā)票集合中計(jì)算7月份到期的總金額。傳遞lambda表達(dá)式以查找月份為7月的發(fā)票,并傳遞方法引用以從發(fā)票中提取金額。

您可能想知道Java編譯器如何在幕后實(shí)現(xiàn)lambda表達(dá)式和方法引用,以及Java虛擬機(jī)(JVM)如何處理它們。例如,lambda表達(dá)式只是匿名內(nèi)部類(lèi)的語(yǔ)法糖嗎?畢竟,可以通過(guò)將lambda表達(dá)式的主體復(fù)制到匿名類(lèi)的相應(yīng)方法的主體中來(lái)翻譯上面的代碼

int total = invoices.stream()
                    .filter(new Predicate<Invoice>() {
                        @Override
                        public boolean test(Invoice inv) {
                            return inv.getMonth() == Month.JULY;
                        }
                    })
                    .mapToInt(new ToIntFunction<Invoice>() {
                        @Override
                        public int applyAsInt(Invoice inv) {
                            return inv.getAmount();
                        }
                    })
                    .sum();

本文將解釋為什么Java編譯器不遵循這種機(jī)制,并將闡明lambda表達(dá)式和方法引用是如何實(shí)現(xiàn)的。我們將研究字節(jié)碼生成,并在實(shí)驗(yàn)室中簡(jiǎn)要分析lambda性能。最后,我們將討論現(xiàn)實(shí)世界中的性能影響。

二、匿名內(nèi)部類(lèi)

匿名內(nèi)部類(lèi)具有可能影響應(yīng)用程序性能的不良特征。

首先,編譯器為每個(gè)匿名內(nèi)部類(lèi)生成一個(gè)新的類(lèi)文件。文件名通??雌饋?lái)像ClassName$1,其中ClassName是定義匿名內(nèi)部類(lèi)的類(lèi)的名稱(chēng),后跟一個(gè)美元符號(hào)和一個(gè)數(shù)字。生成許多類(lèi)文件是不可取的,因?yàn)槊總€(gè)類(lèi)文件在使用之前都需要加載和驗(yàn)證,這會(huì)影響應(yīng)用程序的啟動(dòng)性能。加載可能是一項(xiàng)昂貴的操作,包括磁盤(pán)I/O和解壓縮JAR文件本身。

如果將lambda轉(zhuǎn)換為匿名內(nèi)部類(lèi),則每個(gè)lambda都會(huì)有一個(gè)新的類(lèi)文件。由于每個(gè)匿名內(nèi)部類(lèi)都將被加載,因此它將占用JVM元空間的空間(這是永久生成的Java8替代品)。如果JVM將每個(gè)匿名內(nèi)部類(lèi)中的代碼編譯成機(jī)器代碼,那么它將存儲(chǔ)在代碼緩存中。此外,這些匿名內(nèi)部類(lèi)將被實(shí)例化為單獨(dú)的對(duì)象。因此,匿名內(nèi)部類(lèi)會(huì)增加應(yīng)用程序的內(nèi)存消耗。引入緩存機(jī)制以減少所有這些內(nèi)存開(kāi)銷(xiāo)可能會(huì)有所幫助,這促使引入某種抽象層。

最重要的是,從第一天起選擇使用匿名內(nèi)部類(lèi)實(shí)現(xiàn)lambda將限制未來(lái)lambda實(shí)現(xiàn)更改的范圍,以及它們根據(jù)未來(lái)JVM改進(jìn)而發(fā)展的能力。

讓我們看一下以下代碼:

import java.util.function.Function;
public class AnonymousClassExample {
    Function<String, String> format = new Function<String, String>() {
        public String apply(String input){
            return Character.toUpperCase(input.charAt(0)) + input.substring(1);
        }
    };
}

我們可以使用命令檢查為任何類(lèi)文件生成的字節(jié)碼

javap -c -v ClassName 

為作為匿名內(nèi)部類(lèi)創(chuàng)建的函數(shù)生成的相應(yīng)字節(jié)碼如下所示:

0: aload_0       
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0       
5: new           #2 // class AnonymousClassExample$1
8: dup           
9: aload_0       
10: invokespecial #3 // Method AnonymousClass$1."<init>":(LAnonymousClassExample;)V
13: putfield      #4 // Field format:Ljava/util/function/Function;
16: return  

此代碼顯示以下內(nèi)容:

  • 5:使用字節(jié)碼操作new實(shí)例化匿名類(lèi)示例$1類(lèi)型的對(duì)象。同時(shí)在堆棧上推送對(duì)新創(chuàng)建對(duì)象的引用。
  • 8:dup操作在堆棧上復(fù)制該引用。
  • 10:然后,該值由invokespecial指令使用,該指令初始化匿名內(nèi)部類(lèi)實(shí)例。
  • 13:堆棧頂部現(xiàn)在仍然包含對(duì)對(duì)象的引用,該引用使用putfield指令存儲(chǔ)在AnonymousClassExample類(lèi)的format字段中。

AnonymousClassExample$1是編譯器為匿名內(nèi)部類(lèi)生成的名稱(chēng)。如果您想讓自己放心,還可以檢查AnonymousClassExample$1類(lèi)文件,您將找到函數(shù)接口實(shí)現(xiàn)的代碼。

lambda表達(dá)式轉(zhuǎn)換為匿名內(nèi)部類(lèi)將限制未來(lái)可能的優(yōu)化(例如緩存),因?yàn)樗鼈兣c匿名內(nèi)部類(lèi)字節(jié)碼生成機(jī)制相關(guān)聯(lián)。因此,語(yǔ)言和JVM工程師需要一個(gè)穩(wěn)定的二進(jìn)制表示,該表示提供了足夠的信息,同時(shí)允許JVM在將來(lái)使用其他可能的實(shí)現(xiàn)策略。下一節(jié)將解釋這是如何實(shí)現(xiàn)的!

三、Lambdas和Invokedynamic

為了解決上一節(jié)中解釋的問(wèn)題,Java語(yǔ)言和JVM工程師決定將轉(zhuǎn)換策略的選擇推遲到運(yùn)行時(shí)。Java7引入的新invokedynamic字節(jié)碼指令為他們提供了一種高效實(shí)現(xiàn)這一點(diǎn)的機(jī)制。lambda表達(dá)式到字節(jié)碼的轉(zhuǎn)換分兩步執(zhí)行:

  1. 生成一個(gè)invokedynamic調(diào)用站點(diǎn)(稱(chēng)為lambda工廠),調(diào)用該站點(diǎn)時(shí),該站點(diǎn)返回lambda正在轉(zhuǎn)換到的功能接口的實(shí)例;
  2. lambda表達(dá)式體轉(zhuǎn)換為將通過(guò)invokedynamic指令調(diào)用的方法。

為了說(shuō)明第一步,讓我們檢查編譯包含lambda表達(dá)式的簡(jiǎn)單類(lèi)時(shí)生成的字節(jié)碼,例如:

import java.util.function.Function;

public class Lambda {
    Function<String, Integer> f = s -> Integer.parseInt(s);
}

這將轉(zhuǎn)換為以下字節(jié)碼:

0: aload_0
 1: invokespecial #1 // Method java/lang/Object."<init>":()V
 4: aload_0
 5: invokedynamic #2, 0 // InvokeDynamic
                  #0:apply:()Ljava/util/function/Function;
10: putfield #3 // Field f:Ljava/util/function/Function;
13: return

請(qǐng)注意,方法引用的編譯方式略有不同,因?yàn)閖avac不需要生成合成方法,可以直接引用該方法。

第二步的執(zhí)行方式取決于lambda表達(dá)式是非捕獲(lambda不訪問(wèn)在其主體外部定義的任何變量)還是捕獲(lambda訪問(wèn)在其主體外部定義的變量)。

非捕獲lambda被簡(jiǎn)單地分解為一個(gè)靜態(tài)方法,該方法具有與lambda表達(dá)式完全相同的簽名,并在使用lambda表達(dá)式的同一類(lèi)中聲明。例如,可以將上面lambda類(lèi)中聲明的lambda表達(dá)式分解為如下方法:

static Integer lambda$1(String s) {
    return Integer.parseInt(s);
}

注意:$1不是一個(gè)內(nèi)部類(lèi),它只是我們表示編譯器生成代碼的方式

捕獲lambda表達(dá)式的情況稍微復(fù)雜一些,因?yàn)椴东@的變量必須與lambda的形式參數(shù)一起傳遞給實(shí)現(xiàn)lambda表達(dá)式主體的方法。在這種情況下,常見(jiàn)的轉(zhuǎn)換策略是在lambda表達(dá)式的參數(shù)前面加上每個(gè)捕獲變量的附加參數(shù)。讓我們看一個(gè)實(shí)際的例子:

int offset = 100;
Function<String, Integer> f = s -> Integer.parseInt(s) + offset; 


相應(yīng)的方法實(shí)現(xiàn)可以通過(guò)asy生成:

static Integer lambda$1(int offset, String s) {
    return Integer.parseInt(s) + offset;
}

然而,這種轉(zhuǎn)換策略并不是一成不變的,因?yàn)?code>invokedynamic指令的使用使編譯器能夠靈活地在將來(lái)選擇不同的實(shí)現(xiàn)策略。例如,捕獲的值可以裝箱到數(shù)組中,或者,如果lambda表達(dá)式讀取使用它的類(lèi)的某些字段,則生成的方法可以是實(shí)例方法,而不是聲明為靜態(tài)的,從而避免將這些字段作為附加參數(shù)傳遞。

四、性能表現(xiàn)

這種方法的主要優(yōu)點(diǎn)是性能特性。如果把它們看作是可以簡(jiǎn)化為一個(gè)數(shù)字,那就太好了,但實(shí)際上這里涉及到多個(gè)操作。

第一步是聯(lián)動(dòng)步驟,與上述lambda工廠步驟相對(duì)應(yīng)。如果我們將性能與匿名內(nèi)部類(lèi)進(jìn)行比較,那么等效的操作將是匿名內(nèi)部類(lèi)的類(lèi)加載。Oracle已經(jīng)發(fā)布了Sergey Kuksenko對(duì)這一權(quán)衡的性能分析,您可以看到Kuksenko在2013年JVM語(yǔ)言峰會(huì)上就這一主題發(fā)表了演講[3]。分析表明,需要時(shí)間來(lái)預(yù)熱lambda工廠方法,在此過(guò)程中,初始速度較慢。當(dāng)有足夠多的調(diào)用站點(diǎn)鏈接時(shí),如果代碼位于熱路徑上(即調(diào)用頻率足以編譯JIT的路徑),則性能與類(lèi)加載一致。另一方面,如果是冷路徑,lambda工廠方法可以快100倍。

第二步是從周?chē)秶东@變量。正如我們已經(jīng)提到的,如果沒(méi)有要捕獲的變量,那么可以自動(dòng)優(yōu)化此步驟,以避免使用基于lambda工廠的實(shí)現(xiàn)分配新對(duì)象。在匿名內(nèi)部類(lèi)方法中,我們將實(shí)例化一個(gè)新對(duì)象。為了優(yōu)化等效情況,您必須通過(guò)創(chuàng)建單個(gè)對(duì)象并將其提升到靜態(tài)字段來(lái)手動(dòng)優(yōu)化代碼。例如:

// Hoisted Function
public static final Function<String, Integer> parseInt = new Function<String, Integer>() {
    public Integer apply(String arg) {
        return Integer.parseInt(arg);
    }
}; 

// Usage:
int result = parseInt.apply(“123”);

第三步是調(diào)用實(shí)際方法。目前,匿名內(nèi)部類(lèi)和lambda表達(dá)式都執(zhí)行完全相同的操作,因此這里的性能沒(méi)有差異。非捕獲lambda表達(dá)式的開(kāi)箱即用性能已經(jīng)領(lǐng)先于提升的匿名內(nèi)部類(lèi)。捕獲lambda表達(dá)式的實(shí)現(xiàn)與分配匿名內(nèi)部類(lèi)以捕獲這些字段的性能類(lèi)似。

我們?cè)诒竟?jié)中看到,lambda表達(dá)式的實(shí)現(xiàn)大體上表現(xiàn)良好。雖然匿名內(nèi)部類(lèi)需要手動(dòng)優(yōu)化以避免分配,但JVM已經(jīng)為我們優(yōu)化了最常見(jiàn)的情況(一個(gè)不捕獲其參數(shù)的lambda表達(dá)式)。

當(dāng)然,理解整體性能模型是很好的,但是在實(shí)踐中,事情是如何疊加的呢?我們已經(jīng)在一些軟件項(xiàng)目中使用了Java8,并取得了積極的成果。自動(dòng)優(yōu)化非捕獲lambda可以提供很好的好處。這里有一個(gè)特別的例子,它提出了一些關(guān)于未來(lái)優(yōu)化方向的有趣問(wèn)題。

所討論的示例發(fā)生在處理某些代碼以供系統(tǒng)使用時(shí),該系統(tǒng)需要特別低的GC暫停,理想情況下沒(méi)有。因此,希望避免分配太多的對(duì)象。該項(xiàng)目廣泛使用lambdas來(lái)實(shí)現(xiàn)回調(diào)處理程序。不幸的是,我們?nèi)匀挥邢喈?dāng)多的回調(diào),其中我們沒(méi)有捕獲局部變量,但希望引用當(dāng)前類(lèi)的字段,甚至只調(diào)用當(dāng)前類(lèi)上的方法。目前,這似乎仍然需要分配。下面是一個(gè)代碼示例,旨在闡明我們所討論的內(nèi)容:

public MessageProcessor() {} 

public int processMessages() {
    return queue.read(obj -> {
        if (obj instanceof NewClient) {
            this.processNewClient((NewClient) obj);
        } 
        ...
    });
}

這個(gè)問(wèn)題有一個(gè)簡(jiǎn)單的解決辦法。我們將代碼提升到構(gòu)造函數(shù)中,并將其分配給一個(gè)字段,然后在調(diào)用站點(diǎn)直接引用該字段。下面是我們之前重寫(xiě)的代碼示例:

private final Consumer<Msg> handler; 

public MessageProcessor() {
    handler = obj -> {
        if (obj instanceof NewClient) {
            this.processNewClient((NewClient) obj);
        }
        ...
    };
} 

public int processMessages() {
    return queue.read(handler);
}

在所討論的項(xiàng)目中,這是一個(gè)嚴(yán)重的問(wèn)題:內(nèi)存分析顯示,此模式負(fù)責(zé)前八個(gè)對(duì)象分配站點(diǎn)中的六個(gè),以及應(yīng)用程序總分配的60%以上。

與任何潛在的優(yōu)化一樣,無(wú)論環(huán)境如何,應(yīng)用這種方法都可能會(huì)帶來(lái)其他問(wèn)題。

您選擇編寫(xiě)非慣用代碼純粹是出于性能原因。因此有一個(gè)可讀性權(quán)衡

這也關(guān)系到分配的權(quán)衡。您正在向MessageProcessor添加一個(gè)字段,使其更大,以便分配。相關(guān)lambda的創(chuàng)建和捕獲也會(huì)減慢對(duì)MessageProcessor的構(gòu)造函數(shù)調(diào)用。

我們不是通過(guò)尋找場(chǎng)景,而是通過(guò)內(nèi)存分析發(fā)現(xiàn)了這種情況,并且有一個(gè)很好的業(yè)務(wù)用例證明了優(yōu)化的合理性。我們還處于這樣一個(gè)位置:對(duì)象只分配一次,大量重用lambda表達(dá)式,因此緩存非常有益。與任何性能調(diào)整練習(xí)一樣,通常推薦使用科學(xué)方法。

這也是任何其他最終用戶(hù)尋求優(yōu)化其lambda表達(dá)式使用的方法。嘗試編寫(xiě)干凈、簡(jiǎn)單且功能強(qiáng)大的代碼始終是最好的第一步。任何優(yōu)化,如本次吊裝,應(yīng)僅針對(duì)真正的問(wèn)題進(jìn)行。編寫(xiě)捕獲分配對(duì)象的lambda表達(dá)式本身并不壞——正如編寫(xiě)調(diào)用'new Foo()'的Java代碼本身也不壞一樣。

這一經(jīng)驗(yàn)也確實(shí)表明,要充分利用lambda表達(dá)式,重要的是要習(xí)慣地使用它們。如果lambda表達(dá)式用于表示小的純函數(shù),則它們幾乎不需要從其周?chē)秶东@任何內(nèi)容。和大多數(shù)事情一樣,如果你保持簡(jiǎn)單,事情就會(huì)表現(xiàn)得很好。

結(jié)論
在本文中,我們解釋了lambda不僅僅是隱藏的匿名內(nèi)部類(lèi),以及為什么匿名內(nèi)部類(lèi)不是lambda表達(dá)式的合適實(shí)現(xiàn)方法。通過(guò)lambda表達(dá)式實(shí)現(xiàn)方法,已經(jīng)進(jìn)行了大量的工作。目前,對(duì)于大多數(shù)任務(wù),它們都比匿名內(nèi)部類(lèi)快,但當(dāng)前的狀態(tài)并不完美;測(cè)量驅(qū)動(dòng)的手動(dòng)優(yōu)化仍有一定的空間。

Java8中使用的方法不僅僅局限于Java本身。Scala歷來(lái)通過(guò)生成匿名內(nèi)部類(lèi)來(lái)實(shí)現(xiàn)其lambda表達(dá)式。在Scala2.12中,我們已經(jīng)開(kāi)始使用Java8中引入的lambda元工廠機(jī)制。隨著時(shí)間的推移,JVM上的其他語(yǔ)言也可能采用這種機(jī)制。

到此這篇關(guān)于Java8 LambdaInvokedynamic詳情的文章就介紹到這了,更多相關(guān)Java8 LambdaInvokedynamic內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Springboot中Instant時(shí)間傳參及序列化詳解

    Springboot中Instant時(shí)間傳參及序列化詳解

    這篇文章主要介紹了Springboot中Instant時(shí)間傳參及序列化詳解,Instant是Java8引入的一個(gè)精度極高的時(shí)間類(lèi)型,可以精確到納秒,但實(shí)際使用的時(shí)候不需要這么高的精確度,通常到毫秒就可以了,需要的朋友可以參考下
    2023-11-11
  • java設(shè)計(jì)模式:建造者模式之生產(chǎn)線

    java設(shè)計(jì)模式:建造者模式之生產(chǎn)線

    這篇文章主要介紹了Java設(shè)計(jì)模式之建造者模式,結(jié)合具體實(shí)例形式分析了建造者模式的概念、原理、實(shí)現(xiàn)方法與相關(guān)使用注意事項(xiàng),需要的朋友可以參考下
    2021-08-08
  • springboot框架中如何整合mybatis框架思路詳解

    springboot框架中如何整合mybatis框架思路詳解

    這篇文章主要介紹了springboot框架中如何整合mybatis框架,本文通過(guò)示例圖文相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-12-12
  • 一篇文章帶你搞定JAVA內(nèi)存泄漏

    一篇文章帶你搞定JAVA內(nèi)存泄漏

    今天小編就為大家分享一篇關(guān)于Java內(nèi)存泄漏問(wèn)題處理方法經(jīng)驗(yàn)總結(jié),小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2021-07-07
  • SpringMVC結(jié)合ajaxfileupload.js實(shí)現(xiàn)文件無(wú)刷新上傳

    SpringMVC結(jié)合ajaxfileupload.js實(shí)現(xiàn)文件無(wú)刷新上傳

    這篇文章主要介紹了SpringMVC結(jié)合ajaxfileupload.js實(shí)現(xiàn)文件無(wú)刷新上傳,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • 如何解決線程太多導(dǎo)致java socket連接池出現(xiàn)的問(wèn)題

    如何解決線程太多導(dǎo)致java socket連接池出現(xiàn)的問(wèn)題

    這篇文章主要介紹了如何解決線程太多導(dǎo)致socket連接池出現(xiàn)的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • struts2實(shí)現(xiàn)多文件上傳

    struts2實(shí)現(xiàn)多文件上傳

    這篇文章主要為大家詳細(xì)介紹了struts2實(shí)現(xiàn)多文件上傳,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-08-08
  • Java中如何將符號(hào)分隔的文本文件txt轉(zhuǎn)換為excel

    Java中如何將符號(hào)分隔的文本文件txt轉(zhuǎn)換為excel

    這篇文章主要介紹了Java中如何將符號(hào)分隔的文本文件txt轉(zhuǎn)換為excel,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-09-09
  • SpringBoot整合HikariCP數(shù)據(jù)庫(kù)連接池方式

    SpringBoot整合HikariCP數(shù)據(jù)庫(kù)連接池方式

    這篇文章主要介紹了SpringBoot整合HikariCP數(shù)據(jù)庫(kù)連接池方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • SpringBoot自動(dòng)裝配Condition的實(shí)現(xiàn)方式

    SpringBoot自動(dòng)裝配Condition的實(shí)現(xiàn)方式

    這篇文章主要介紹了SpringBoot自動(dòng)裝配Condition的實(shí)現(xiàn)方式,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-08-08

最新評(píng)論