91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

導致JVM物理內存消耗大的Bug是怎么樣的

發布時間:2021-10-23 17:29:48 來源:億速云 閱讀:144 作者:柒染 欄目:大數據

導致JVM物理內存消耗大的Bug是怎么樣的,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

概述

最近我們公司在幫一個客戶查一個JVM的問題(JDK1.8.0_191-b12),發現一個系統老是被OS Kill掉,是內存泄露導致的。在查的過程中,陰差陽錯地發現了JVM另外的一個Bug。這個Bug可能會導致大量物理內存被使用,我們已經反饋給了社區,并得到快速反饋,預計在OpenJDK8最新版中發布(JDK11中也存在這個問題)。 導致JVM物理內存消耗大的Bug是怎么樣的

PS:用戶的那個問題最終也解決了,定位下來算是C2的一個設計缺陷導致大量內存被使用,安全性上沒有得到保障。

找出消耗大內存的線程

接下來主要分享下這個BUG的發現過程,先要客戶實時跟蹤進程的情況,當內存使用明顯上升的時候,通過/proc/<pid>/smaps,看到了不少64MB的內存分配,Rss也基本消耗完了。

7fd690000000-7fd693f23000 rw-p 00000000 00:00 0 Size:              64652 kBRss:               64652 kBPss:               64652 kBShared_Clean:          0 kBShared_Dirty:          0 kBPrivate_Clean:         0 kBPrivate_Dirty:     64652 kBReferenced:        64652 kBAnonymous:         64652 kBAnonHugePages:         0 kBSwap:                  0 kBKernelPageSize:        4 kBMMUPageSize:           4 kBLocked:                0 kBVmFlags: rd wr mr mw me nr sd 7fd693f23000-7fd694000000 ---p 00000000 00:00 0 Size:                884 kBRss:                   0 kBPss:                   0 kBShared_Clean:          0 kBShared_Dirty:          0 kBPrivate_Clean:         0 kBPrivate_Dirty:         0 kBReferenced:            0 kBAnonymous:             0 kBAnonHugePages:         0 kBSwap:                  0 kBKernelPageSize:        4 kBMMUPageSize:           4 kBLocked:                0 kBVmFlags: mr mw me nr sd

再通過strace命令跟蹤了下系統調用,再回到上面的虛擬地址,我們找到了相關的mmap系統調用

[pid    71] 13:34:41.982589 mmap(0x7fd690000000, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x7fd690000000 <0.000107>

執行mmap的線程是71號線程,接著通過jstack把線程dump出來,找到了對應的線程其實是C2 CompilerThread0

"C2 CompilerThread0" #39 daemon prio=9 os_prio=0 tid=0x00007fd8acebb000 nid=0x47 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

最后再grep了一下strace的輸出,果然看到這個線程在大量的進行內存分配,總共有2G多。

經典的64M問題

對于64M的問題,是一個非常經典的問題,在JVM中并沒有這種大量分配64M大小的邏輯,因此可以排除JVM特定意義的分配。這其實是glibc里針對malloc函數分配內存的一種機制,glibc從2.10開始提供的一種機制,為了分配內存更加高效,glibc提供了arena的機制,默認情況下在64位下每一個arena的大小是64M,下面是64M的計算邏輯,其中sizeof(long)為8

define DEFAULT_MMAP_THRESHOLD_MAX (4 * 1024 * 1024 * sizeof(long))define HEAP_MAX_SIZE (2 * DEFAULT_MMAP_THRESHOLD_MAX)
p2 = (char *) MMAP (aligned_heap_area, HEAP_MAX_SIZE, PROT_NONE,
                          MAP_NORESERVE);

一個進程最多能分配的arena個數在64位下是8 * core,32位下是2 * core個

#define NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8))
 {              int n = __get_nprocs ();              if (n >= 1)
                narenas_limit = NARENAS_FROM_NCORES (n);              else
                /* We have no information about the system.  Assume two
                   cores.  */
                narenas_limit = NARENAS_FROM_NCORES (2);
            }

這種分配機制的好處,主要是應對多線程的環境,為每個核留有幾個64M的緩存塊,這樣線程在分配內存的時候因為沒有鎖而變得更高效,如果達到上限了就會去慢速的main_arena里分配了。

可以通過設置環境變量MALLOC_ARENA_MAX來設置64M塊的個數,當我們設置為1的時候就會發現這些64M的內存塊都沒有了,然后都集中分配到一個大區域了,也就是main_arena,說明這個參數生效了。

無意的發現

再回過來思考為什么C2線程會出現大于2G的內存消耗的時候,無意中跟蹤C2這塊代碼發現了如下代碼可能會導致大量內存消耗,這個代碼的位置是nmethod.cpp的nmethod::metadata_do方法,不過這塊如果真的發生的話,肯定不是看到C2的線程大量分配,而是看到VMThread這個線程,因為下面這塊代碼主要是它執行的。

void nmethod::metadata_do(void f(Metadata*)) {
  address low_boundary = verified_entry_point();  if (is_not_entrant()) {
    low_boundary += NativeJump::instruction_size;    // %%% Note:  On SPARC we patch only a 4-byte trap, not a full NativeJump.
    // (See comment above.)
  }
  {    // Visit all immediate references that are embedded in the instruction stream.
    RelocIterator iter(this, low_boundary);    while (iter.next()) {      if (iter.type() == relocInfo::metadata_type ) {
        metadata_Relocation* r = iter.metadata_reloc();        // In this metadata, we must only follow those metadatas directly embedded in
        // the code.  Other metadatas (oop_index>0) are seen as part of
        // the metadata section below.
        assert(1 == (r->metadata_is_immediate()) +
               (r->metadata_addr() >= metadata_begin() && r->metadata_addr() < metadata_end()),
               “metadata must be found in exactly one place”);        if (r->metadata_is_immediate() && r->metadata_value() != NULL) {
          Metadata* md = r->metadata_value();          if (md != _method) f(md);
        }
      } else if (iter.type() == relocInfo::virtual_call_type) {        // Check compiledIC holders associated with this nmethod
        CompiledIC *ic = CompiledIC_at(&iter);        if (ic->is_icholder_call()) {
          CompiledICHolder* cichk = ic->cached_icholder();
          f(cichk->holder_metadata());
          f(cichk->holder_klass());
        } else {
          Metadata* ic_oop = ic->cached_metadata();          if (ic_oop != NULL) {
            f(ic_oop);
          }
        }
      }
    }
  }
inline CompiledIC* CompiledIC_at(RelocIterator* reloc_iter) {
  assert(reloc_iter->type() == relocInfo::virtual_call_type ||
      reloc_iter->type() == relocInfo::opt_virtual_call_type, "wrong reloc. info");
  CompiledIC* c_ic = new CompiledIC(reloc_iter);
  c_ic->verify();  return c_ic;
}

注意上面的CompiledIC *ic = CompiledIC_at(&iter);這段代碼,因為CompiledIC是一個ResourceObj,這種資源會在c heap里分配(malloc),不過他們是和線程進行關聯的,假如我們在某處代碼聲明了ResourceMark,那當執行到這里的時候會標記當前的位置,再接下來線程要分配內存的時候如果線程關聯的內存不夠用,就會malloc一塊插進去并被管理起來,否則會實現內存的復用。當ResourceMark析構函數執行的時候,會將之前的位置還原,后面這個線程如果要分配內存又會從這個位置開始復用內存塊。注意這里說的內存塊和上面的64M內存塊不是一個概念。

因為這段代碼在while循環里,因此存在非常多次數的重復調用,這樣明明在執行完一次之后可以復用內存的地方并不能復用,而可能會導致大量的內存被不斷分配。表現起來可能就是物理內存消耗很大,遠大于Xmx。

這個修復辦法也很簡單,就是在CompiledIC *ic = CompiledIC_at(&iter);前加上ResourceMark rm;即可。

這個問題主要發生的場景是針對頻繁大量做Class Retransform或者Class Redefine的場景。所以如果系統里有這種agent的時候還是要稍微注意下這個問題。

這個問題發現后我們給社區提了patch,不過后面發現再JDK12中其實已經修復了,但是在之前的版本里的都沒有修復,這個問題提交給社區后,有人很快響應了,并可能在OpenJDK1.8.0-212中被fix。

最后在這里也簡單提下客戶那邊的那個問題,之所以C2線程消耗太大,最主要的原因是存在非常大的方法需要編譯,而這個編譯的過程是需要大量的內存消耗的,正因為如此,才會導致內存突然暴增,所以給大家一個建議,方法不要寫太大啦,如果這個方法調用還很頻繁,那真的會很悲劇的。

看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

读书| 涿州市| 巨鹿县| 霍林郭勒市| 云林县| 潜江市| 宜兴市| 清涧县| 拉萨市| 汝阳县| 永吉县| 聂荣县| 乌拉特前旗| 绥德县| 突泉县| 余庆县| 城市| 东山县| 孝昌县| 博客| 长子县| 永泰县| 信丰县| 平武县| 纳雍县| 德保县| 长顺县| 噶尔县| 百色市| 汝州市| 宁城县| 巍山| 手机| 炎陵县| 巴林左旗| 阿瓦提县| 金川县| 绥芬河市| 沙田区| 芜湖县| 聂荣县|