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

歡迎訪問(wèn) 生活随笔!

生活随笔

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

编程问答

JVM调优:卡表(CardTable)简介

發(fā)布時(shí)間:2024/3/7 编程问答 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM调优:卡表(CardTable)简介 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

我們知道,JVM在進(jìn)行垃圾收集時(shí),需要先標(biāo)記所有可達(dá)對(duì)象,然后再清除不可達(dá)對(duì)象,釋放內(nèi)存空間。那么,如何快速的找到所有可達(dá)對(duì)象呢?

最簡(jiǎn)單粗暴的實(shí)現(xiàn),就是每次進(jìn)行垃圾收集時(shí),都對(duì)整個(gè)堆中的所有對(duì)象進(jìn)行掃描,找到所有存活對(duì)象。邏輯是簡(jiǎn)單,但性能比較差。

簡(jiǎn)單粗暴的實(shí)現(xiàn)方式,通常都是不可取的。那JVM是如何實(shí)現(xiàn)快速標(biāo)記可達(dá)對(duì)象的?

答案是GC Roots。

GC Roots是垃圾收集器尋找可達(dá)對(duì)象的起點(diǎn),通過(guò)這些起始引用,可以快速的遍歷出存活對(duì)象。GC Roots最常見(jiàn)的是靜態(tài)引用和堆棧的局部引用變量。然而,這不是我們這講的重點(diǎn):)

現(xiàn)代JVM,堆空間通常被劃分為新生代和老年代。由于新生代的垃圾收集通常很頻繁,如果老年代對(duì)象引用了新生代的對(duì)象,那么,需要跟蹤從老年代到新生代的所有引用,從而避免每次YGC時(shí)掃描整個(gè)老年代,減少開(kāi)銷。

對(duì)于HotSpot JVM,使用了卡標(biāo)記(Card Marking)技術(shù)來(lái)解決老年代到新生代的引用問(wèn)題。具體是,使用卡表(Card Table)和寫(xiě)屏障(Write Barrier)來(lái)進(jìn)行標(biāo)記并加快對(duì)GC Roots的掃描。

卡表(Card Table)

基于卡表(Card Table)的設(shè)計(jì),通常將堆空間劃分為一系列2次冪大小的卡頁(yè)(Card Page)。

卡表(Card Table),用于標(biāo)記卡頁(yè)的狀態(tài),每個(gè)卡表項(xiàng)對(duì)應(yīng)一個(gè)卡頁(yè)。

HotSpot JVM的卡頁(yè)(Card Page)大小為512字節(jié),卡表(Card Table)被實(shí)現(xiàn)為一個(gè)簡(jiǎn)單的字節(jié)數(shù)組,即卡表的每個(gè)標(biāo)記項(xiàng)為1個(gè)字節(jié)。

當(dāng)對(duì)一個(gè)對(duì)象引用進(jìn)行寫(xiě)操作時(shí)(對(duì)象引用改變),寫(xiě)屏障邏輯將會(huì)標(biāo)記對(duì)象所在的卡頁(yè)為dirty。

OpenJDK/Oracle 1.6/1.7/1.8 JVM默認(rèn)的卡標(biāo)記簡(jiǎn)化邏輯如下:

CARD_TABLE [this address >> 9] = 0;

首先,計(jì)算對(duì)象引用所在卡頁(yè)的卡表索引號(hào)。將地址右移9位,相當(dāng)于用地址除以512(2的9次方)。可以這么理解,假設(shè)卡表卡頁(yè)的起始地址為0,那么卡表項(xiàng)0、1、2對(duì)應(yīng)的卡頁(yè)起始地址分別為0、512、1024(卡表項(xiàng)索引號(hào)乘以卡頁(yè)512字節(jié))。

其次,通過(guò)卡表索引號(hào),設(shè)置對(duì)應(yīng)卡標(biāo)識(shí)為dirty。

?

?

?

帶來(lái)的2個(gè)問(wèn)題

1.無(wú)條件寫(xiě)屏障帶來(lái)的性能開(kāi)銷

每次對(duì)引用的更新,無(wú)論是否更新了老年代對(duì)新生代對(duì)象的引用,都會(huì)進(jìn)行一次寫(xiě)屏障操作。顯然,這會(huì)增加一些額外的開(kāi)銷。但是,與YGC時(shí)掃描整個(gè)老年代相比較,這個(gè)開(kāi)銷就低得多了。

不過(guò),在高并發(fā)環(huán)境下,寫(xiě)屏障又帶來(lái)了虛共享(false sharing)問(wèn)題。

2.高并發(fā)下虛共享帶來(lái)的性能開(kāi)銷

在高并發(fā)情況下,頻繁的寫(xiě)屏障很容易發(fā)生虛共享(false sharing),從而帶來(lái)性能開(kāi)銷。

假設(shè)CPU緩存行大小為64字節(jié),由于一個(gè)卡表項(xiàng)占1個(gè)字節(jié),這意味著,64個(gè)卡表項(xiàng)將共享同一個(gè)緩存行。

HotSpot每個(gè)卡頁(yè)為512字節(jié),那么一個(gè)緩存行將對(duì)應(yīng)64個(gè)卡頁(yè)一共64*512=32KB。

如果不同線程對(duì)對(duì)象引用的更新操作,恰好位于同一個(gè)32KB區(qū)域內(nèi),這將導(dǎo)致同時(shí)更新卡表的同一個(gè)緩存行,從而造成緩存行的寫(xiě)回、無(wú)效化或者同步操作,間接影響程序性能。

一個(gè)簡(jiǎn)單的解決方案,就是不采用無(wú)條件的寫(xiě)屏障,而是先檢查卡表標(biāo)記,只有當(dāng)該卡表項(xiàng)未被標(biāo)記過(guò)才將其標(biāo)記為dirty。

這就是JDK 7中引入的解決方法,引入了一個(gè)新的JVM參數(shù)-XX:+UseCondCardMark,在執(zhí)行寫(xiě)屏障之前,先簡(jiǎn)單的做一下判斷。如果卡頁(yè)已被標(biāo)識(shí)過(guò),則不再進(jìn)行標(biāo)識(shí)。

簡(jiǎn)單理解如下:

if (CARD_TABLE [this address >> 9] != 0)CARD_TABLE [this address >> 9] = 0;

與原來(lái)的實(shí)現(xiàn)相比,只是簡(jiǎn)單的增加了一個(gè)判斷操作。

雖然開(kāi)啟-XX:+UseCondCardMark之后多了一些判斷開(kāi)銷,但是卻可以避免在高并發(fā)情況下可能發(fā)生的并發(fā)寫(xiě)卡表問(wèn)題。通過(guò)減少并發(fā)寫(xiě)操作,進(jìn)而避免出現(xiàn)虛共享問(wèn)題(false sharing)。

也用于CMS GC

CMS在并發(fā)標(biāo)記階段,應(yīng)用線程和GC線程是并發(fā)執(zhí)行的,因此可能產(chǎn)生新的對(duì)象或?qū)ο箨P(guān)系發(fā)生變化,例如:

  • 新生代的對(duì)象晉升到老年代;
  • 直接在老年代分配對(duì)象;
  • 老年代對(duì)象的引用關(guān)系發(fā)生變更;
  • 等等。

對(duì)于這些對(duì)象,需要重新標(biāo)記以防止被遺漏。為了提高重新標(biāo)記的效率,并發(fā)標(biāo)記階段會(huì)把這些發(fā)生變化的對(duì)象所在的Card標(biāo)識(shí)為Dirty,這樣后續(xù)階段就只需要掃描這些Dirty Card的對(duì)象,從而避免掃描整個(gè)老年代。

參見(jiàn):Java之CMS GC的7個(gè)階段


https://ezlippi.com/blog/2018/01/jvm-card-table-turning.html

網(wǎng)上關(guān)于JVM調(diào)優(yōu)的文章很多,這篇文章主要介紹JVM里Card Table的作用。我們知道JVM GC可以分為MinorGC、MajorGC和FullGC,對(duì)于Mirnor GC來(lái)講它的耗時(shí)主要由兩個(gè)因素決定:

  • 復(fù)制活躍對(duì)象的時(shí)間
  • 掃描card table(老年代對(duì)象引用新生代對(duì)象)的時(shí)間
  • Java虛擬機(jī)用了一個(gè)叫做CardTable(卡表)的數(shù)據(jù)結(jié)構(gòu)來(lái)標(biāo)記老年代的某一塊內(nèi)存區(qū)域中的對(duì)象是否持有新生代對(duì)象的引用,卡表的數(shù)量取決于老年代的大小和每張卡對(duì)應(yīng)的內(nèi)存大小,每張卡在卡表中對(duì)應(yīng)一個(gè)比特位,當(dāng)老年代中的某個(gè)對(duì)象持有了新生代對(duì)象的引用時(shí),JVM就把這個(gè)對(duì)象對(duì)應(yīng)的Card所在的位置標(biāo)記為dirty(bit位設(shè)置為1),這樣在Minor GC時(shí)就不用掃描整個(gè)老年代,而是掃描Card為Dirty對(duì)應(yīng)的那些內(nèi)存區(qū)域。

    這樣子可以提高效率減少M(fèi)inorGC的停頓時(shí)間。

    在JVM中,一個(gè)Card的大小是512字節(jié),在多個(gè)線程并行收集時(shí),JVM通過(guò)ParGCCardsPerStrideChunk參數(shù)設(shè)置每個(gè)線程每次掃描的Card數(shù)量,默認(rèn)是256,相當(dāng)于是把老年代分成許多strides,每個(gè)線程每次掃描一個(gè)stride,每個(gè)stride大小為512*256 = 128K,如果你的老年代大小為4G,那總共有4G/128K=32K個(gè)Strides。多線程在掃描這么多的strides時(shí)就涉及到調(diào)度和分配的問(wèn)題,stride數(shù)量太多就會(huì)導(dǎo)致線程在stride之間切換的開(kāi)銷增加,進(jìn)而導(dǎo)致GC暫停時(shí)間增長(zhǎng)。因此JVM提供了ParGCCardsPerStrideChunk這個(gè)參數(shù)來(lái)配置每個(gè)stride對(duì)應(yīng)的card數(shù)量,這個(gè)數(shù)量要根據(jù)實(shí)際的業(yè)務(wù)場(chǎng)景進(jìn)行調(diào)優(yōu),網(wǎng)上一般流傳3個(gè)魔術(shù)數(shù)字:32768、4K和8K。

    -XX:+UnlockDiagnosticVMOptions -XX:ParGCCardsPerStrideChunk=4096

    這個(gè)值不能設(shè)置的太大,因?yàn)镚C線程需要掃描這個(gè)stride中老年代對(duì)象持有的新生代對(duì)象的引用,如果只有少量引用新生代的對(duì)象那就導(dǎo)致浪費(fèi)了很多時(shí)間在根本不需要掃描的對(duì)象上。

    參考文檔:

  • Secret HotSpot option improving GC pauses on large heaps
  • Advanced JVM and GC tuning
  • 參考

    psy-lob-saw.blogspot.com/2014/10/the…

    blogs.oracle.com/dave/false-…

    bibliography.selflanguage.org/_static/wri…

    www.memorymanagement.org/glossary/b.…

    www.memorymanagement.org/glossary/w.…

    docs.oracle.com/cd/E19205-0…

    ifeve.com/falsesharin…

    ------------------------------------

  • 總結(jié)

    以上是生活随笔為你收集整理的JVM调优:卡表(CardTable)简介的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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