TLAB整理

TLAB整理

  • HotSpot VM在JAVA堆中對象創(chuàng)建,布局,訪問全過程(僅限于普通java對象,不包括數(shù)組和Class對象等)

    • 對象創(chuàng)建

      • vm遇到new指令時 檢查指令的參數(shù)是否能在常量池中定位到一個類的符號引用 并檢查這個符號引用代表的類是否已經(jīng)加載,解析,初始化過
      • 如果沒有則先執(zhí)行類加載啸罢;在類加載檢查通過后,為新對象分配內(nèi)存,對象所需內(nèi)存大小在類加載完后完全確定;
      • 堆是規(guī)整的 (用過的內(nèi)存在一遍,沒有用過的在另一邊,中間一個指針)
        • 分配內(nèi)存直接挪動指針忘空閑的那邊 - 指針碰撞 bump the pointer
      • 堆不規(guī)整
        • 已使用內(nèi)存和空閑內(nèi)存相互交錯,維護一個列表記錄哪些內(nèi)存是可用的,空閑列表 free-list
      • 垃圾回收器是否帶有壓縮整理功能
        • Serial, ParNew等帶有Compact過程的收集器時 采用分配算法是 指針碰撞
        • CMS這種基于標記-清除的通常采用 空閑列表 算法
      • 并發(fā)問題
        • 即便上面指針碰撞也會出現(xiàn)并發(fā)問題 因為創(chuàng)建對象分配空間太頻繁了
        • solution
          • 1.分配空間的動作進行同步處理(實際上VM采用cas+失敗重試的方式保證原子性)
          • 2诵竭,預(yù)先給線程分配一塊空間TLAB,后面分配空間先在TLAB上分配,TLAB不夠了再從堆上分配
            • -XX:+UseTLAB
      • 內(nèi)存分配完后,vm將分配到的內(nèi)存空間都初始化為0(不包括對象頭) TLAB的情況下清0過程可以提前至TLAB分配時
      • vm對對象進行必要的設(shè)置:設(shè)置對象頭信息等
      • 執(zhí)行<init>方法按程序員的意愿初始化對象
      • bytecodeinterpreter.cpp源碼
    • 對象在內(nèi)存中的布局

      • 對象頭

        • Mark Word(對象自身運行時數(shù)據(jù): hashcode, gc年齡, 鎖狀態(tài)標識, 線程持有的鎖, 偏向線程ID, 偏向時間戳等)
          • mark word在32谷徙,64bits JVM中分別是32bit, 64bit(為開啟指針壓縮)
          • 對象自身運行時需要存儲的信息很多,所以mark word被設(shè)計成一個非固定的數(shù)據(jù)結(jié)構(gòu),根據(jù)對象狀態(tài)復(fù)用存儲空間
        • 類型指針(哪個類的實例) 但不是所有的查找對象的元數(shù)據(jù)信息都有經(jīng)過對象本身
        • 記錄數(shù)組長度
          • 如果對象是一個JAVA數(shù)組
        • 通過普通java對象的元數(shù)據(jù)信息可以確定java對象的大小,但是從數(shù)組的元數(shù)據(jù)無法確定數(shù)組的大小(數(shù)組長度存儲在對象頭中)
        • markOop.cpp
          • mark word
      • 實例數(shù)據(jù)

        • 存儲順序
          • 分配策略參數(shù)
            • longs/doubles, ints, shorts/chars, bytes/booleans, oops(ordinary object pointer) (默認分配策略)
            • 相同字寬的字段總是分配到一起, 父類定義的變量在子類之前
          • 字段在java源代碼中定義順序
      • 對齊填充

        • 對象起始地址必須是8字節(jié)的整數(shù)倍 也就是對象的大小必須是8字節(jié)的整數(shù)倍 而對象頭部分正好是8字節(jié)的倍數(shù)
    • 對象訪問

      • Java程序通過棧上的reference數(shù)據(jù)來操作堆上的具體對象(只規(guī)定了reference一個指向?qū)ο蟮囊?
        • 句柄
          • reference(java棧本地變量表)->句柄池(java heap)->兩個指針分別指向:實例池,方法區(qū)
          • 對象被移動,reference本身不需要修改;reference存儲的是穩(wěn)定的句柄地址
        • 直接指針
          • reference->堆中對象->方法區(qū)中的對象類型
          • 速度更快 節(jié)省一次指針定位時間 對象的訪問太頻繁
      • HotSopt VM 使用第二種驱敲,直接指針定位方式
  • TLAB細節(jié)

    • 什么是TLAB
      • TLAB全稱ThreadLocalAllocBuffer提完,是線程的一塊私有內(nèi)存百新,如果設(shè)置了虛擬機參數(shù) -XX:+UseTLAB珊燎,在線程初始化時,同時也會申請一塊指定大小的內(nèi)存姑廉,只給當前線程使用缺亮,這樣每個線程都單獨擁有一個Buffer,如果需要分配內(nèi)存桥言,就在自己的Buffer上分配萌踱,這樣就不存在競爭的情況,可以大大提升分配效率号阿,當Buffer容量不夠的時候虫蝶,再重新從Eden區(qū)域申請一塊繼續(xù)使用,這個申請動作還是需要原子操作的(CAS+重試)
      • TLAB的目的是在為新對象分配內(nèi)存空間時倦西,讓每個Java應(yīng)用線程能在使用自己專屬的分配指針來分配空間能真,均攤對GC堆(eden區(qū))里共享的分配指針做更新而帶來的同步開銷
      • TLAB只是讓每個線程有私有的分配指針,但底下存對象的內(nèi)存空間還是給所有線程訪問的,只是其它線程無法在這個區(qū)域分配而已粉铐。當一個TLAB用滿(分配指針top撞上分配極限end了)疼约,就新申請一個TLAB,而在老TLAB里的對象還留在原地什么都不用管——它們無法感知自己是否是曾經(jīng)從TLAB分配出來的蝙泼,而只關(guān)心自己是在eden里分配的程剥。
    • TLAB實現(xiàn)
      • 源碼 openjdk/hotspot/src/share/vm/memory/threadLocalAllocBuffer.hpp
      • TLAB簡單來說本質(zhì)上就是三個指針:start,top 和 end汤踏,每個線程都會從Eden分配一大塊空間织鲸,例如說100KB,作為自己的TLAB溪胶,其中 start 和 end 是占位用的搂擦,標識出 eden 里被這個 TLAB 所管理的區(qū)域,卡住eden里的一塊空間不讓其它線程來這里分配哗脖。而 top 就是里面的分配指針瀑踢,一開始指向跟 start 同樣的位置,然后逐漸分配才避,直到再要分配下一個對象就會撞上 end 的時候就會觸發(fā)一次 TLAB refill橱夭,refill過程后續(xù)會解釋。
      • _desired_size 是指TLAB的內(nèi)存大小桑逝。
      • _refill_waste_limit 是指最大的浪費空間棘劣,假設(shè)為5KB,通俗一點講就是:
        • 1楞遏、假如當前TLAB已經(jīng)分配96KB茬暇,還剩下4KB,但是現(xiàn)在new了一個對象需要6KB的空間橱健,顯然TLAB的內(nèi)存不夠了,這時可以簡單的重新申請一個TLAB沙廉,原先的TLAB交給Eden管理拘荡,這時只浪費4KB的空間,在_refill_waste_limit 之內(nèi)撬陵。
        • 2珊皿、假如當前TLAB已經(jīng)分配90KB,還剩下10KB巨税,現(xiàn)在new了一個對象需要11KB蟋定,顯然TLAB的內(nèi)存不夠了,這時就不能簡單的拋棄當前TLAB草添,這11KB會被安排到Eden區(qū)進行申請驶兜。
      • 在Java代碼中執(zhí)行new Thread()的時候,會觸發(fā)以下代碼
        // The first routine called by a new Java thread void JavaThread::run() { // initialize thread-local alloc buffer related fields this->initialize_tlab(); // used to test validitity of stack trace backs this->record_base_of_stack_pointer(); // Record real stack base and size. this->record_stack_base_and_size(); // Initialize thread local storage; set before calling MutexLocker this->initialize_thread_local_storage(); this->create_stack_guard_pages(); this->cache_global_variables(); }
        void initialize_tlab() { if (UseTLAB) { tlab().initialize(); } }
      • 其中tlab()返回的就是一個ThreadLocalAllocBuffer對象,調(diào)用initialize()初始化TLAB抄淑,實現(xiàn)如下
        • 1屠凶、設(shè)置當前TLAB的_desired_size,該值通過initial_desired_size()方法計算肆资;
          • 字段_desired_size的計算過程分析
            • TLABSize在argument模塊中默認會設(shè)置大小為 256 * K矗愧,也可以通過JVM參數(shù)選擇進行設(shè)置,不過即使設(shè)置了也會和一個最大值max_size進行比較郑原,然后取一個較小值唉韭,其中max_size計算如下:
              • 這里明確說明了TLAB的大小不能超過可以容納 int[Integer.MAX_VALUE]
        • 2、設(shè)置當前TLAB的_refill_waste_limit犯犁,該值通過initial_refill_waste_limit()方法計算属愤;
          • 字段_refill_waste_limit計算分析
            size_t initial_refill_waste_limit() { return desired_size() / TLABRefillWasteFraction(默認64); }
        • 3、初始化一些統(tǒng)計字段栖秕,如_number_of_refills春塌、_fast_refill_waste、_slow_refill_waste簇捍、_gc_waste和_slow_allocations只壳;
    • 內(nèi)存分配
      • 對象的內(nèi)存分配入口為instanceKlass::allocate_instance(),通過CollectedHeap::obj_allocate()方法在堆內(nèi)存上進行分配
      • 其中common_mem_allocate_init()方法最終會調(diào)用CollectedHeap::common_mem_allocate_noinit()方法暑塑,實現(xiàn)如下
        • 根據(jù)UseTLAB的值吼句,決定是否在TLAB上進行內(nèi)存分配,如果JVM參數(shù)中沒有手動取消UseTLAB事格,會調(diào)用allocate_from_tlab()在TLAB上嘗試分配惕艳,因為可能存在分配失敗的情況,比如TLAB容量不足驹愚,看下allocate_from_tlab()的實現(xiàn):
          • 從上述實現(xiàn)可以看出远搪,先會嘗試調(diào)用ThreadLocalAllocBuffer 的 allocate 方法,如果返回為空逢捺,再執(zhí)行allocate_from_tlab_slow()進行分配谁鳍,從這個方法命名可以看出這是比較慢的分配路徑。
            • ThreadLocalAllocBuffer 的 allocate 方法實現(xiàn)如下:
              • 通過判斷當前TLAB的剩余容量是否大于需要分配的大小劫瞳,來決定分配結(jié)果倘潜,如果當前剩余容量不夠,就返回NULL志于,表示分配失敗涮因。
          • 慢分配allocate_from_tlab_slow()實現(xiàn)如下
            • 1、如果當前TLAB的剩余容量大于浪費閾值伺绽,就不在當前TLAB分配养泡,直接在共享的Eden區(qū)進行分配嗜湃,并且記錄慢分配的內(nèi)存大小瓤荔;
            • 2净蚤、如果剩余容量小于浪費閾值,說明可以丟棄當前TLAB了输硝;
            • 3今瀑、通過allocate_new_tlab()方法,從eden新分配一塊裸的空間出來(這一步可能會失數惆选)橘荠,如果失敗說明eden沒有足夠空間來分配這個新TLAB,就會觸發(fā)YGC郎逃。
            • 申請好新的TLAB內(nèi)存之后哥童,執(zhí)行TLAB的fill()方法,實現(xiàn)如下:
              • 1褒翰、統(tǒng)計refill的次數(shù)
              • 2贮懈、初始化重新申請到的內(nèi)存塊
              • 3、將當前TLAB拋棄(retire)掉优训,這個過程中最重要的動作是將TLAB末尾尚未分配給Java對象的空間(浪費掉的空間)分配成一個假的“filler object”(目前是用int[]作為filler object)朵你。這是為了保持GC堆可以線性parse(heap parseability)用的。
  • RednaxelaFX揣非、你假笨關(guān)于TLAB的一些分析總結(jié)

    • TLAB refill包括下述幾個動作:
      • 將當前TLAB拋棄(retire)掉抡医。這個過程中最重要的動作是將TLAB末尾尚未分配給Java對象的空間(浪費掉的空間)分配成一個假的“filler object”(目前是int[]作為filler object)。這是為了保持GC堆可以線性parse(heap parseability)用的早敬。
      • 從eden新分配一塊裸的空間出來(這一步可能會失敿缮怠)
      • 將新分配的空間范圍記錄到ThreadLocalAllocBuffer里TLAB refill不成功(eden沒有足夠空間來分配這個新TLAB)就會觸發(fā)YGC。
    • 注意“撞上”指的是在某次分配請求中搞监,top + new_obj_size >= end 的情況水孩,也就是說在被判定“撞上”的時候,top 常常離 end 還有一段距離琐驴,只是這之間的空間不足以滿足新對象的分配請求 new_obj_size 的大小俘种。這意味著在觸發(fā)TLAB refill的時候,有可能會浪費掉位于該TLAB末尾的一部分空間:該TLAB已經(jīng)占用了這塊空間所以其它線程無法在這里分配Java對象棍矛,但該TLAB要refill的話它自己也不會在這塊空間繼續(xù)分配Java對象安疗,從應(yīng)用層面看這塊空間就浪費了抛杨。
    • 每次分配TLAB的大小不是固定的够委,而是每個線程根據(jù)該線程啟動開始到現(xiàn)在的歷史統(tǒng)計信息來自己單獨調(diào)整的。如果一個線程上跑的代碼的內(nèi)存分配速率非常高怖现,則該線程會選擇使用更大的TLAB以達到均攤同步開銷的效果茁帽,反之亦然玉罐;同時它還會統(tǒng)計浪費比例,并且將其放入計算新TLAB大小的考慮因素當中潘拨,把浪費比例控制在一定范圍內(nèi)吊输。
    • GC很重要的一點是對heap parseability的依賴。GC做某些需要線性掃描堆里的對象的操作時铁追,需要知道堆里哪些地方有對象而哪些地方是空洞季蚂。一種辦法是使用外部數(shù)據(jù)結(jié)構(gòu),例如freelist或者allocation BitMap之類來記錄哪里有空洞琅束;另一種辦法是把空洞部分也假裝成有對象扭屁,這樣GC在線性遍歷時會看到一個“對象總是連續(xù)分配的”的假象,就可以以統(tǒng)一的方式來遍歷:遍歷到一個對象時涩禀,通過其對象頭記錄的信息找出該對象的大小料滥,然后跳到該大小之后就可以找到下一個對象的對象頭,依此類推艾船。HotSpot選擇的是后者的做法葵腹,假裝成有對象的這種東西就叫做filler object(填充對象)。
    • PLAB也是個非常有趣的東西屿岂,提到TLAB的話也可以順帶說下PLAB践宴。HotSpot里的TLAB是只在eden里分配的,用于給新建的小對象用雁社。(本來其實也有考慮讓TLAB在任意位置分配浴井,但后來沒實現(xiàn))。PLAB則是在old gen里分配的一種臨時的結(jié)構(gòu)霉撵。就是笨神說的promotion LAB磺浙。
    • 在多GC線程并行做YGC的時候,大家都要為了晉升對象而在old gen里分配空間徒坡,于是old gen的分配指針就熱起來了撕氧。大量的競爭會使得并行度降低,所以跟TLAB用同樣的思路喇完,old gen在處理YGC的晉升對象的分配也一樣可以用(GC)線程私有的分配區(qū)伦泥。這就是PLAB。另外在CMS里old gen的剩余空間不是連續(xù)的锦溪,而是有很多空洞不脯。這些剩余空間是通過freelist來管理的。
    • 如果ParNew要把對象晉升到CMS管理的old gen刻诊,不優(yōu)化的話就得在freelist上做分配防楷。于是就可以通過類似PLAB的方式,每個GC線程先從freelist申請一塊大空間则涯,然后在這塊大空間里線性分配(bump pointer)复局。這樣就既降低了對分配指針/freelist的競爭冲簿,又可以降低freelist分配的頻率而轉(zhuǎn)為用線性分配。
  • References

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末峦剔,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子角钩,更是在濱河造成了極大的恐慌吝沫,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件递礼,死亡現(xiàn)場離奇詭異野舶,居然都是意外死亡,警方通過查閱死者的電腦和手機宰衙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門平道,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人供炼,你說我怎么就攤上這事一屋。” “怎么了袋哼?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵冀墨,是天一觀的道長。 經(jīng)常有香客問我涛贯,道長诽嘉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任弟翘,我火速辦了婚禮虫腋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘稀余。我一直安慰自己悦冀,他們只是感情好,可當我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布睛琳。 她就那樣靜靜地躺著盒蟆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪师骗。 梳的紋絲不亂的頭發(fā)上历等,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天,我揣著相機與錄音辟癌,去河邊找鬼寒屯。 笑死,一個胖子當著我的面吹牛愿待,可吹牛的內(nèi)容都是我干的浩螺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼仍侥,長吁一口氣:“原來是場噩夢啊……” “哼要出!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起农渊,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤患蹂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后砸紊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年赁还,在試婚紗的時候發(fā)現(xiàn)自己被綠了潮梯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡游添,死狀恐怖系草,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情唆涝,我是刑警寧澤找都,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站廊酣,受9級特大地震影響能耻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜亡驰,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一晓猛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧凡辱,春花似錦鞍帝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至续徽,卻和暖如春蚓曼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钦扭。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工纫版, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人客情。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓其弊,卻偏偏與公主長得像癞己,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子梭伐,可洞房花燭夜當晚...
    茶點故事閱讀 44,974評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 一 痹雅、java虛擬機底層結(jié)構(gòu)詳解 我們知道,一個JVM實例的行為不光是它自己的事糊识,還涉及到它的子系統(tǒng)绩社、存儲區(qū)域、...
    葡萄喃喃囈語閱讀 1,486評論 0 4
  • 從三月份找實習(xí)到現(xiàn)在赂苗,面了一些公司愉耙,掛了不少,但最終還是拿到小米拌滋、百度朴沿、阿里、京東败砂、新浪悯仙、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,253評論 11 349
  • JVM架構(gòu) 當一個程序啟動之前吠卷,它的class會被類裝載器裝入方法區(qū)(Permanent區(qū))锡垄,執(zhí)行引擎讀取方法區(qū)的...
    cocohaifang閱讀 1,666評論 0 7
  • http://www.cnblogs.com/angeldevil/p/3801189.html值得一看 Clas...
    snail_knight閱讀 1,425評論 1 0
  • 因為我總是做著各種各樣的夢
    海秀啊閱讀 213評論 0 0