日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JVM系列之:Contend注解和false-sharing

發(fā)布時(shí)間:2024/2/28 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM系列之:Contend注解和false-sharing 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • 簡(jiǎn)介
  • false-sharing的由來
    • 怎么解決?
  • 使用JOL分析
  • Contended在JDK9中的問題
  • padded和unpadded性能對(duì)比
  • Contended在JDK中的使用
  • 總結(jié)

簡(jiǎn)介

現(xiàn)代CPU為了提升性能都會(huì)有自己的緩存結(jié)構(gòu),而多核CPU為了同時(shí)正常工作,引入了MESI,作為CPU緩存之間同步的協(xié)議。MESI雖然很好,但是不當(dāng)?shù)臅r(shí)候用也可能導(dǎo)致性能的退化。

到底怎么回事呢?一起來看看吧。

false-sharing的由來

為了提升處理速度,CPU引入了緩存的概念,我們先看一張CPU緩存的示意圖:

CPU緩存是位于CPU與內(nèi)存之間的臨時(shí)數(shù)據(jù)交換器,它的容量比內(nèi)存小的多但是交換速度卻比內(nèi)存要快得多。

CPU的讀實(shí)際上就是層層緩存的查找過程,如果所有的緩存都沒有找到的情況下,就是主內(nèi)存中讀取。

為了簡(jiǎn)化和提升緩存和內(nèi)存的處理效率,緩存的處理是以Cache Line(緩存行)為單位的。

一次讀取一個(gè)Cache Line的大小到緩存。

在mac系統(tǒng)中,你可以使用sysctl machdep.cpu.cache.linesize來查看cache line的大小。
在linux系統(tǒng)中,使用getconf LEVEL1_DCACHE_LINESIZE來獲取cache line的大小。

本機(jī)中cache line的大小是64字節(jié)。

考慮下面一個(gè)對(duì)象:

public class CacheLine {public long a;public long b; }

很簡(jiǎn)單的對(duì)象,通過之前的文章我們可以指定,這個(gè)CacheLine對(duì)象的大小應(yīng)該是12字節(jié)的對(duì)象頭+8字節(jié)的long+8字節(jié)的long+4字節(jié)的補(bǔ)全,總共應(yīng)該是32字節(jié)。

因?yàn)?2字節(jié)< 64字節(jié),所以一個(gè)cache line就可以將其包括。

現(xiàn)在問題來了,如果是在多線程的環(huán)境中,thread1對(duì)a進(jìn)行累加,而thread2對(duì)b進(jìn)行累加。會(huì)發(fā)生什么情況呢?

  • 第一步,新創(chuàng)建出來的對(duì)象被存儲(chǔ)到CPU1和CPU2的緩存cache line中。
  • thread1使用CPU1對(duì)對(duì)象中的a進(jìn)行累計(jì)。
  • 根據(jù)CPU緩存之間的同步協(xié)議MESI(這個(gè)協(xié)議比較復(fù)雜,這里就先不展開講解),因?yàn)镃PU1對(duì)緩存中的cache line進(jìn)行了修改,所以CPU2中的這個(gè)cache line的副本對(duì)象將會(huì)被標(biāo)記為I(Invalid)無效狀態(tài)。
  • thread2使用CPU2對(duì)對(duì)象中的b進(jìn)行累加,這個(gè)時(shí)候因?yàn)镃PU2中的cache line已經(jīng)被標(biāo)記為無效了,所以必須重新從主內(nèi)存中同步數(shù)據(jù)。
  • 大家注意,耗時(shí)點(diǎn)就在第4步。 雖然a和b是兩個(gè)不同的long,但是因?yàn)樗麄儽话谕粋€(gè)cache line中,最終導(dǎo)致了雖然兩個(gè)線程沒有共享同一個(gè)數(shù)值對(duì)象,但是還是發(fā)送了鎖的關(guān)聯(lián)情況。

    怎么解決?

    那怎么解決這個(gè)問題呢?

    在JDK7之前,我們需要使用一些空的字段來手動(dòng)補(bǔ)全。

    public class CacheLine { public long actualValue; public long p0, p1, p2, p3, p4, p5, p6, p7; }

    像上面那樣,我們手動(dòng)填充一些空白的long字段,從而讓真正的actualValue可以獨(dú)占一個(gè)cache line,就沒有這些問題了。

    但是在JDK8之后,java文件的編譯期會(huì)將無用的變量自動(dòng)忽略掉,那么上面的方法就無效了。

    還好,JDK8中引入了sun.misc.Contended注解,使用這個(gè)注解會(huì)自動(dòng)幫我們補(bǔ)全字段。

    使用JOL分析

    接下來,我們使用JOL工具來分析一下Contended注解的對(duì)象和不帶Contended注解的對(duì)象有什么區(qū)別。

    @Test public void useJol() {log.info("{}", ClassLayout.parseClass(CacheLine.class).toPrintable());log.info("{}", ClassLayout.parseInstance(new CacheLine()).toPrintable());log.info("{}", ClassLayout.parseClass(CacheLinePadded.class).toPrintable());log.info("{}", ClassLayout.parseInstance(new CacheLinePadded()).toPrintable());}

    注意,在使用JOL分析Contended注解的對(duì)象時(shí)候,需要加上 -XX:-RestrictContended參數(shù)。

    同時(shí)可以設(shè)置-XX:ContendedPaddingWidth 來控制padding的大小。

    INFO com.flydean.CacheLineJOL - com.flydean.CacheLine object internals:OFFSET SIZE TYPE DESCRIPTION VALUE0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)8 4 (object header) d0 29 17 00 (11010000 00101001 00010111 00000000) (1518032)12 4 (alignment/padding gap) 16 8 long CacheLine.valueA 024 8 long CacheLine.valueB 0 Instance size: 32 bytes Space losses: 4 bytes internal + 0 bytes external = 4 bytes total INFO com.flydean.CacheLineJOL - com.flydean.CacheLinePadded object internals:OFFSET SIZE TYPE DESCRIPTION VALUE0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)8 4 (object header) d2 5d 17 00 (11010010 01011101 00010111 00000000) (1531346)12 4 (alignment/padding gap) 16 8 long CacheLinePadded.b 024 128 (alignment/padding gap) 152 8 long CacheLinePadded.a 0 Instance size: 160 bytes Space losses: 132 bytes internal + 0 bytes external = 132 bytes total

    我們看到使用了Contended的對(duì)象大小是160字節(jié)。直接填充了128字節(jié)。

    Contended在JDK9中的問題

    sun.misc.Contended是在JDK8中引入的,為了解決填充問題。

    但是大家注意,Contended注解是在包sun.misc,這意味著一般來說是不建議我們直接使用的。

    雖然不建議大家使用,但是還是可以用的。

    但如果你使用的是JDK9-JDK14,你會(huì)發(fā)現(xiàn)sun.misc.Contended沒有了!

    因?yàn)镴DK9引入了JPMS(Java Platform Module System),它的結(jié)構(gòu)跟JDK8已經(jīng)完全不一樣了。

    經(jīng)過我的研究發(fā)現(xiàn),sun.misc.Contended, sun.misc.Unsafe,sun.misc.Cleaner這樣的類都被移到了jdk.internal.**中,并且是默認(rèn)不對(duì)外使用的。

    那么有人要問了,我們換個(gè)引用的包名是不是就行了?

    import jdk.internal.vm.annotation.Contended;

    抱歉還是不行。

    error: package jdk.internal.vm.annotation is not visible@jdk.internal.vm.annotation.Contended^(package jdk.internal.vm.annotation is declared in modulejava.base, which does not export it to the unnamed module)

    好,我們找到問題所在了,因?yàn)槲覀兊拇a并沒有定義module,所以是一個(gè)默認(rèn)的“unnamed” module,我們需要把java.base中的jdk.internal.vm.annotation使unnamed module可見。

    要實(shí)現(xiàn)這個(gè)目標(biāo),我們可以在javac中添加下面的flag:

    --add-exports java.base/jdk.internal.vm.annotation=ALL-UNNAMED

    好了,現(xiàn)在我們可以正常通過編譯了。

    padded和unpadded性能對(duì)比

    上面我們看到padded對(duì)象大小是160字節(jié),而unpadded對(duì)象的大小是32字節(jié)。

    對(duì)象大了,運(yùn)行的速度會(huì)不慢呢?

    實(shí)踐出真知,我們使用JMH工具在多線程環(huán)境中來對(duì)其進(jìn)行測(cè)試:

    @State(Scope.Benchmark) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Fork(value = 1, jvmArgsPrepend = "-XX:-RestrictContended") @Warmup(iterations = 10) @Measurement(iterations = 25) @Threads(2) public class CacheLineBenchMark {private CacheLine cacheLine= new CacheLine();private CacheLinePadded cacheLinePadded = new CacheLinePadded();@Group("unpadded")@GroupThreads(1)@Benchmarkpublic long updateUnpaddedA() {return cacheLine.a++;}@Group("unpadded")@GroupThreads(1)@Benchmarkpublic long updateUnpaddedB() {return cacheLine.b++;}@Group("padded")@GroupThreads(1)@Benchmarkpublic long updatePaddedA() {return cacheLinePadded.a++;}@Group("padded")@GroupThreads(1)@Benchmarkpublic long updatePaddedB() {return cacheLinePadded.b++;}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(CacheLineBenchMark.class.getSimpleName()).build();new Runner(opt).run();} }

    上面的JMH代碼中,我們使用兩個(gè)線程分別對(duì)A和B進(jìn)行累計(jì)操作,看下最后的運(yùn)行結(jié)果:

    從結(jié)果看來雖然padded生成的對(duì)象比較大,但是因?yàn)锳和B在不同的cache line中,所以不會(huì)出現(xiàn)不同的線程去主內(nèi)存取數(shù)據(jù)的情況,因此要執(zhí)行的比較快。

    Contended在JDK中的使用

    其實(shí)Contended注解在JDK源碼中也有使用,不算廣泛,但是都很重要。

    比如在Thread中的使用:

    比如在ConcurrentHashMap中的使用:

    其他使用的地方:Exchanger,ForkJoinPool,Striped64。

    感興趣的朋友可以仔細(xì)研究一下。

    總結(jié)

    Contented從最開始的sun.misc到現(xiàn)在的jdk.internal.vm.annotation,都是JDK內(nèi)部使用的class,不建議大家在應(yīng)用程序中使用。

    這就意味著我們之前使用的方式是不正規(guī)的,雖然能夠達(dá)到效果,但是不是官方推薦的。那么我們還有沒有什么正規(guī)的辦法來解決false-sharing的問題呢?

    有知道的小伙伴歡迎留言給我討論!

    本文作者:flydean程序那些事

    本文鏈接:http://www.flydean.com/jvm-contend-false-sharing/

    本文來源:flydean的博客

    歡迎關(guān)注我的公眾號(hào):程序那些事,更多精彩等著您!

    總結(jié)

    以上是生活随笔為你收集整理的JVM系列之:Contend注解和false-sharing的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。