主頁 > 後端開發 > 帶你認識JDK8中超nice的Native Memory Tracking

帶你認識JDK8中超nice的Native Memory Tracking

2022-10-14 12:47:05 後端開發

摘要:從 OpenJDK8 起有了一個很 nice 的虛擬機內部功能: Native Memory Tracking (NMT),

本文分享自華為云社區《Native Memory Tracking 詳解(1):基礎介紹》,作者:畢昇小助手,

0.引言

我們經常會好奇,我啟動了一個 JVM,他到底會占據多大的記憶體?他的記憶體都消耗在哪里?為什么 JVM 使用的記憶體比我設定的 -Xmx 大這么多?我的記憶體設定引數是否合理?為什么我的 JVM 記憶體一直緩慢增長?為什么我的 JVM 會被 OOMKiller 等等,這都涉及到 JAVA 虛擬機對記憶體的一個使用情況,不如讓我們來一探其中究竟,

1.簡介

除去大家都熟悉的可以使用 -Xms、-Xmx 等引數設定的堆(Java Heap),JVM 還有所謂的非堆記憶體(Non-Heap Memory),

可以通過一張圖來簡單看一下 Java 行程所使用的記憶體情況(簡略情況):

非堆記憶體包括方法區和Java虛擬機內部做處理或優化所需的記憶體,

  • 方法區:在所有執行緒之間共享,存盤每個類的結構,如運行時常量池、欄位和方法資料,以及方法和建構式的代碼,方法區在邏輯上(虛擬機規范)是堆的一部分,但規范并不限定實作方法區的記憶體位置和編譯代碼的管理策略,所以不同的 Java 虛擬機可能有不同的實作方式,此處我們僅討論 HotSpot,
  • 除了方法區域外,Java 虛擬機實作可能需要記憶體用于內部的處理或優化,例如,JIT編譯器需要記憶體來存盤從Java虛擬機代碼轉換的本機代碼(儲存在CodeCache中),以獲得高性能,

從 OpenJDK8 起有了一個很 nice 的虛擬機內部功能: Native Memory Tracking (NMT) ,我們可以使用 NMT 來追蹤了解 JVM 的記憶體使用詳情(即上圖中的 JVM Memory 部分),幫助我們排查記憶體增長與記憶體泄漏相關的問題,

2.如何使用

2.1 開啟 NMT

默認情況下,NMT是處于關閉狀態的,我們可以通過設定 JVM 啟動引數來開啟:-XX:NativeMemoryTracking=[off | summary | detail],

注意:啟用NMT會導致5% -10%的性能開銷,

NMT 使用選項如下表所示:

我們注意到,如果想使用 NMT 觀察 JVM 的記憶體使用情況,我們必須重啟 JVM 來設定XX:NativeMemoryTracking 的相關選項,但是重啟會使得我們丟失想要查看的現場,只能等到問題復現時才能繼續觀察,

筆者試圖通過一種不用重啟 JVM 的方式來開啟 NMT ,但是很遺憾目前沒有這樣的功能,

JVM 啟動后只有被標記為 manageable 的引數才可以動態修改或者說賦值,我們可以通過 JDK management interface (com.sun.management.HotSpotDiagnosticMXBean API) 或者 jinfo -flag 命令來進行動態修改的操作,讓我們看下所有可以被修改的引數值(JDK8):

java -XX:+PrintFlagsFinal | grep manageable
intx CMSAbortablePrecleanWaitMillis = 100 {manageable}
intx CMSTriggerInterval = -1 {manageable}
intx CMSWaitDuration = 2000 {manageable}
bool HeapDumpAfterFullGC = false {manageable}
bool HeapDumpBeforeFullGC = false {manageable}
bool HeapDumpOnOutOfMemoryError = false {manageable}
ccstr HeapDumpPath = {manageable}
uintx MaxHeapFreeRatio = 100 {manageable}
uintx MinHeapFreeRatio = 0 {manageable}
bool PrintClassHistogram = false {manageable}
bool PrintClassHistogramAfterFullGC = false {manageable}
bool PrintClassHistogramBeforeFullGC = false {manageable}
bool PrintConcurrentLocks = false {manageable}
bool PrintGC = false {manageable}
bool PrintGCDateStamps = false {manageable}
bool PrintGCDetails = false {manageable}
bool PrintGCID = false {manageable}
bool PrintGCTimeStamps = false {manageable}

很顯然,其中不包含 NativeMemoryTracking ,

2.2 使用 jcmd 訪問 NMT 資料

我們可以通過 jcmd 命令來很方便的查看 NMT 相關的資料:

jcmd VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]

jcmd 操作 NMT 選項如下表所示:

  • NMT 默認列印的報告是 KB 來進行呈現的,為了滿足我們不同的需求,我們可以使用scale=MB | GB來更加直觀的列印資料,
  • 創建 baseline 之后使用 diff 功能可以很直觀地對比出兩次 NMT 資料之間的差距,

看到 shutdown 選項,筆者本能的一激靈,既然我們可以通過 shutdown 來關閉 NMT ,那為什么不能通過逆向 shutdown 功能來動態的開啟 NMT 呢?筆者找到 shutdown 相關原始碼(以下都是基于 OpenJDK 8):

# hotspot/src/share/vm/services/nmtDCmd.cpp
void NMTDCmd::execute(DCmdSource source, TRAPS) {
 // Check NMT state
 // native memory tracking has to be on
 if (MemTracker::tracking_level() == NMT_off) {
 output()->print_cr("Native memory tracking is not enabled");
 return;
  } else if (MemTracker::tracking_level() == NMT_minimal) {
 output()->print_cr("Native memory tracking has been shutdown");
 return;
  }
  ......
 //執行 shutdown 操作
 else if (_shutdown.value()) {
 MemTracker::shutdown();
 output()->print_cr("Native memory tracking has been turned off");
  }
  ......
}
# hotspot/src/share/vm/services/memTracker.cpp
// Shutdown can only be issued via JCmd, and NMT JCmd is serialized by lock
void MemTracker::shutdown() {
 // We can only shutdown NMT to minimal tracking level if it is ever on.
 if (tracking_level () > NMT_minimal) {
 transition_to(NMT_minimal);
  }
}
# hotspot/src/share/vm/services/nmtCommon.hpp
// Native memory tracking level  //NMT的追蹤等級
enum NMT_TrackingLevel {
 NMT_unknown = 0xFF,
 NMT_off     = 0x00,
 NMT_minimal = 0x01,
 NMT_summary = 0x02,
 NMT_detail  = 0x03
};

遺憾的是通過原始碼我們發現,shutdown 操作只是將 NMT 的追蹤等級 tracking_level 變成了 NMT_minimal 狀態(而并不是直接變成了 off 狀態),注意注釋:We can only shutdown NMT to minimal tracking level if it is ever on(即我們只能將NMT關閉到最低跟蹤級別,如果它曾經打開),

這就導致了如果我們沒有開啟過 NMT ,那就沒辦法通過魔改 shutdown 操作逆向打開 NMT ,因為 NMT 追蹤的部分記憶體只在 JVM 啟動初始化的階段進行記錄(如在初始化堆記憶體分配的程序中通過 NMT_TrackingLevel level = MemTracker::tracking_level(); 來獲取 NMT 的追蹤等級,視等級來記錄記憶體使用情況),JVM 啟動之后再開啟 NMT 這部分記憶體的使用情況就無法記錄,所以目前來看,還是只能在重啟 JVM 后開啟 NMT,

至于提供 shutdown 功能的原因,應該就是讓用戶在開啟 NMT 功能之后如果想要關閉,不用再次重啟 JVM 行程,shutdown 會清理虛擬記憶體用來追蹤的資料結構,并停止一些追蹤的操作(如記錄 malloc 記憶體的分配)來降低開啟 NMT 帶來的性能耗損,并且通過原始碼可以發現 tracking_level 變成 NMT_minimal 狀態后也不會再執行 jcmd VM.native_memory 命令相關的操作,

2.3 虛擬機退出時獲取 NMT 資料

除了在虛擬機運行時獲取 NMT 資料,我們還可以通過兩個引數:-XX:+UnlockDiagnosticVMOptions和-XX:+PrintNMTStatistics ,來獲取虛擬機退出時記憶體使用情況的資料(輸出資料的詳細程度取決于你設定的跟蹤級別,如 summary/detail 等),

-XX:+UnlockDiagnosticVMOptions:解鎖用于診斷 JVM 的選項,默認關閉,

-XX:+PrintNMTStatistics:當啟用 NMT 時,在虛擬機退出時列印記憶體使用情況,默認關閉,需要開啟前置引數 -XX:+UnlockDiagnosticVMOptions 才能正常使用,

3.NMT 記憶體 & OS 記憶體概念差異性

我們可以做一個簡單的測驗,使用如下引數啟動 JVM :

-Xmx1G -Xms1G -XX:+UseG1GC -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=256m -XX:ReservedCodeCacheSize=256M -XX:NativeMemoryTracking=detail

然后使用 NMT 查看記憶體使用情況(因各環境資源引數不一樣,部分未明確設定資料可能由虛擬機根據資源自行計算得出,以下資料僅供參考):

jcmd VM.native_memory detail

NMT 會輸出如下日志:

Native Memory Tracking:
Total: reserved=2813709KB, committed=1497485KB
-                 Java Heap (reserved=1048576KB, committed=1048576KB)
                            (mmap: reserved=1048576KB, committed=1048576KB) 
-                     Class (reserved=1056899KB, committed=4995KB)
                            (classes #442)
                            (malloc=131KB #259) 
                            (mmap: reserved=1056768KB, committed=4864KB) 
-                    Thread (reserved=258568KB, committed=258568KB)
                            (thread #127)
                            (stack: reserved=258048KB, committed=258048KB)
                            (malloc=390KB #711) 
                            (arena=130KB #234)
-                      Code (reserved=266273KB, committed=4001KB)
                            (malloc=33KB #309) 
                            (mmap: reserved=266240KB, committed=3968KB) 
-                        GC (reserved=164403KB, committed=164403KB)
                            (malloc=92723KB #6540) 
                            (mmap: reserved=71680KB, committed=71680KB) 
-                  Compiler (reserved=152KB, committed=152KB)
                            (malloc=4KB #36) 
                            (arena=148KB #21)
-                  Internal (reserved=14859KB, committed=14859KB)
                            (malloc=14827KB #3632) 
                            (mmap: reserved=32KB, committed=32KB) 
-                    Symbol (reserved=1423KB, committed=1423KB)
                            (malloc=936KB #111) 
                            (arena=488KB #1)
-    Native Memory Tracking (reserved=330KB, committed=330KB)
                            (malloc=118KB #1641) 
                            (tracking overhead=211KB)
-               Arena Chunk (reserved=178KB, committed=178KB)
                            (malloc=178KB) 
-                   Unknown (reserved=2048KB, committed=0KB)
                            (mmap: reserved=2048KB, committed=0KB) 
    ......

大家可能會發現 NMT 所追蹤的記憶體(即 JVM 中的 Reserved、Committed)與作業系統 OS (此處指Linux)的記憶體概念存在一定的差異性,

首先按我們理解的作業系統的概念:

作業系統對記憶體的分配管理典型地分為兩個階段:保留(reserve)和提交(commit),保留階段告知系統從某一地址開始到后面的dwSize大小的連續虛擬記憶體需要供程式使用,行程其他分配記憶體的操作不得使用這段記憶體;提交階段將虛擬地址映射到對應的真實物理記憶體中,這樣這塊記憶體就可以正常使用 [1],

如果使用 top 或者 smem 等命令查看剛才啟動的 JVM 行程會發現:

top
   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 36257 dou+      20 0 10.8g  54200 17668 S  99.7 0.0 13:04.15 java

此時疑問就產生了,為什么 NMT 中的 committed ,即日志詳情中 Total: reserved=2813709KB, committed=1497485KB 中的 1497485KB 與 top 中 RES 的大小54200KB 存在如此大的差異?

使用 man 查看 top 中 RES 的概念(不同版本 Linux 可能不同):

RES  --  Resident Memory Size (KiB)
 A  subset of the virtual address space (VIRT) representing the non-swapped physical memory a task is currently using.  It is also the sum of the RSan,
 RSfd and RSsh fields.
           It can include private anonymous pages, private pages mapped to files (including program images and shared libraries)  plus  shared  anonymous  pages.
           All such memory is backed by the swap file represented separately under SWAP.
           Lastly, this field may also include shared file-backed pages which, when modified, act as a dedicated swap file and thus will never impact SWAP.

RES 表示任務當前使用的非交換物理記憶體(此時未發生swap),那按對作業系統 commit 提交記憶體的理解,這兩者貌似應該對上,為何現在差距那么大呢?

筆者一開始猜測是 JVM 的 uncommit 機制(如 JEP 346[2],支持 G1 在空閑時自動將 Java 堆記憶體回傳給作業系統,BiSheng JDK 對此做了增強與改進[3])造成的,JVM 在 uncommit 將記憶體返還給 OS 之后,NMT 沒有除去返還的記憶體導致統計錯誤,

但是在翻閱了原始碼之后發現,G1 在 shrink 縮容的時候,通常呼叫鏈路如下:

G1CollectedHeap::shrink ->

G1CollectedHeap::shrink_helper ->

HeapRegionManager::shrink_by ->

HeapRegionManager::uncommit_regions ->

G1PageBasedVirtualSpace::uncommit ->

G1PageBasedVirtualSpace::uncommit_internal ->

os::uncommit_memory

忽略細節,uncommit 會在最后呼叫 os::uncommit_memory ,查看 os::uncommit_memory 原始碼:

bool os::uncommit_memory(char* addr, size_t bytes) {
 bool res;
 if (MemTracker::tracking_level() > NMT_minimal) {
    Tracker tkr = MemTracker::get_virtual_memory_uncommit_tracker();
    res = pd_uncommit_memory(addr, bytes);
 if (res) {
 tkr.record((address)addr, bytes);
    }
  } else {
    res = pd_uncommit_memory(addr, bytes);
  }
 return res;
}

可以發現在返還 OS 記憶體之后,MemTracker 是進行了統計的,所以此處的誤差不是由 uncommit 機制造成的,

既然如此,那又是由什么原因造成的呢?筆者在追蹤 JVM 的記憶體分配邏輯時發現了一些端倪,此處以Code Cache(存放 JVM 生成的 native code、JIT編譯、JNI 等都會編譯代碼到 native code,其中 JIT 生成的 native code 占用了 Code Cache 的絕大部分空間)的初始化分配為例,其大致呼叫鏈路為下:

InitializeJVM ->

Thread::vreate_vm ->

init_globals ->

codeCache_init ->

CodeCache::initialize ->

CodeHeap::reserve ->

VirtualSpace::initialize ->

VirtualSpace::initialize_with_granularity ->

VirtualSpace::expand_by ->

os::commit_memory

查看 os::commit_memory 相關原始碼:

bool os::commit_memory(char* addr, size_t size, size_t alignment_hint,
 bool executable) {
 bool res = os::pd_commit_memory(addr, size, alignment_hint, executable);
 if (res) {
 MemTracker::record_virtual_memory_commit((address)addr, size, CALLER_PC);
  }
 return res;
}

我們發現 MemTracker 在此記錄了 commit 的記憶體供 NMT 用以統計計算,繼續查看 os::pd_commit_memory 原始碼,可以發現其呼叫了 os::Linux::commit_memory_impl 函式,

查看 os::Linux::commit_memory_impl 原始碼:

int os::Linux::commit_memory_impl(char* addr, size_t size, bool exec) {
 int prot = exec ? PROT_READ|PROT_WRITE|PROT_EXEC : PROT_READ|PROT_WRITE;
 uintptr_t res = (uintptr_t) ::mmap(addr, size, prot,
                                   MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0);
 if (res != (uintptr_t) MAP_FAILED) {
 if (UseNUMAInterleaving) {
 numa_make_global(addr, size);
    }
 return 0;
  }
 int err = errno;  // save errno from mmap() call above
 if (!recoverable_mmap_error(err)) {
 warn_fail_commit_memory(addr, size, exec, err);
    vm_exit_out_of_memory(size, OOM_MMAP_ERROR, "committing reserved memory.");
  }
 return err;
}

問題的原因就在 uintptr_t res = (uintptr_t) ::mmap(addr, size, prot, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0); 這段代碼上,

我們發現,此時申請記憶體執行的是 mmap 函式,并且傳遞的 port 引數是 PROT_READ|PROT_WRITE|PROT_EXEC 或 PROT_READ|PROT_WRITE ,使用 man 查看 mmap ,其中相關描述為:

The  prot  argument describes the desired memory protection of the mapping (and must not conflict with the open mode of the file).  It is either PROT_NONE
 or the bitwise OR of one or more of the following flags:
       PROT_EXEC  Pages may be executed.
       PROT_READ  Pages may be read.
       PROT_WRITE Pages may be written.
       PROT_NONE  Pages may not be accessed.

由此我們可以看出,JVM 中所謂的 commit 記憶體,只是將記憶體 mmaped 映射為可讀可寫可執行的狀態!而在 Linux 中,在分配記憶體時又是 lazy allocation 的機制,只有在行程真正訪問時才分配真實的物理記憶體,所以 NMT 中所統計的 committed 并不是對應的真實的物理記憶體,自然與 RES 等統計方式無法對應起來,

所以 JVM 為我們提供了一個引數 -XX:+AlwaysPreTouch,使我們可以在啟動之初就按照記憶體頁粒度都訪問一遍 Heap,強制為其分配物理記憶體以減少運行時再分配記憶體造成的延遲(但是相應的會影響 JVM 行程初始化啟動的時間),查看相關代碼:

void os::pretouch_memory(char* start, char* end) {
 for (volatile char *p = start; p < end; p += os::vm_page_size()) {
    *p = 0;
  }
}

讓我們來驗證下,開啟 -XX:+AlwaysPreTouch 前后的效果,

NMT 的 heap 地址范圍:

Virtual memory map:
[0x00000000c0000000 - 0x0000000100000000] reserved 1048576KB for Java Heap from
    [0x0000ffff93ea36d8] ReservedHeapSpace::ReservedHeapSpace(unsigned long, unsigned long, bool, char*)+0xb8
    [0x0000ffff93e67f68] Universe::reserve_heap(unsigned long, unsigned long)+0x2d0
    [0x0000ffff93898f28] G1CollectedHeap::initialize()+0x188
    [0x0000ffff93e68594] Universe::initialize_heap()+0x15c
 [0x00000000c0000000 - 0x0000000100000000] committed 1048576KB from
            [0x0000ffff938bbe8c] G1PageBasedVirtualSpace::commit_internal(unsigned long, unsigned long)+0x14c
            [0x0000ffff938bc08c] G1PageBasedVirtualSpace::commit(unsigned long, unsigned long)+0x11c
            [0x0000ffff938bf774] G1RegionsLargerThanCommitSizeMapper::commit_regions(unsigned int, unsigned long)+0x5c
            [0x0000ffff93943f54] HeapRegionManager::commit_regions(unsigned int, unsigned long)+0x7c

對應該地址的/proc/{pid}/smaps:

//開啟前                                                 //開啟后
c0000000-100080000 rw-p 00000000 00:00 0                c0000000-100080000 rw-p 00000000 00:00 0
Size:            1049088 kB                             Size:            1049088 kB
KernelPageSize:        4 kB                             KernelPageSize:        4 kB
MMUPageSize:           4 kB                             MMUPageSize:           4 kB
Rss:                 792 kB                             Rss:             1049088 kB
Pss:                 792 kB                             Pss:             1049088 kB
Shared_Clean:          0 kB                             Shared_Clean:          0 kB
Shared_Dirty:          0 kB                             Shared_Dirty:          0 kB
Private_Clean:         0 kB                             Private_Clean:         0 kB
Private_Dirty:       792 kB                             Private_Dirty:   1049088 kB
Referenced:          792 kB                             Referenced:      1048520 kB
Anonymous:           792 kB                             Anonymous:       1049088 kB
LazyFree:              0 kB                             LazyFree:              0 kB
AnonHugePages:         0 kB                             AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB                             ShmemPmdMapped:        0 kB
Shared_Hugetlb:        0 kB                             Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB                             Private_Hugetlb:       0 kB
Swap:                  0 kB                             Swap:                  0 kB
SwapPss:               0 kB                             SwapPss:               0 kB
Locked:                0 kB                             Locked:                0 kB
VmFlags: rd wr mr mw me ac                              VmFlags: rd wr mr mw me ac

對應的/proc/{pid}/status:

//開啟前                                            //開啟后
    ...                                                ...
    VmHWM:    54136 kB                                VmHWM:   1179476 kB
    VmRSS:    54136 kB                                VmRSS:   1179476 kB
    ...                                                ...
    VmSwap:        0 kB                                VmSwap:        0 kB
    ...

開啟引數后的 top:

PID USER  PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 85376 dou+  20 0 10.8g   1.1g  17784 S  99.7 0.4 14:56.31 java

觀察對比我們可以發現,開啟 AlwaysPreTouch 引數后,NMT 統計的 commited 已經與 top 中的 RES 差不多了,之所以不完全相同是因為該引數只能 Pre-touch 分配 Java heap 的物理記憶體,至于其他的非 heap 的記憶體,還是受到 lazy allocation 機制的影響,

同理我們可以簡單看下 JVM 的 reserve 機制:

# hotspot/src/share/vm/runtime/os.cpp
char* os::reserve_memory(size_t bytes, char* addr, size_t alignment_hint,
   MEMFLAGS flags) {
 char* result = pd_reserve_memory(bytes, addr, alignment_hint);
 if (result != NULL) {
 MemTracker::record_virtual_memory_reserve((address)result, bytes, CALLER_PC);
 MemTracker::record_virtual_memory_type((address)result, flags);
  }
 return result;
}
# hotspot/src/os/linux/vm/os_linux.cpp
char* os::pd_reserve_memory(size_t bytes, char* requested_addr,
 size_t alignment_hint) {
 return anon_mmap(requested_addr, bytes, (requested_addr != NULL));
}
static char* anon_mmap(char* requested_addr, size_t bytes, bool fixed) {
  ......
 addr = (char*)::mmap(requested_addr, bytes, PROT_NONE,
                       flags, -1, 0);
  ......
}

reserve 通過 mmap(requested_addr, bytes, PROT_NONE, flags, -1, 0); 來將記憶體映射為 PROT_NONE,這樣其他的 mmap/malloc 等就不能呼叫使用,從而達到了 guard memory 或者說 guard pages 的目的,

OpenJDK 社區其實也注意到了 NMT 記憶體與 OS 記憶體差異性的問題,所以社區也提出了相應的 Enhancement 來增強功能:

1.JDK-8249666[4] :

    • 目前 NMT 將分配的記憶體顯示為 Reserved 或 Committed,而在 top 或 pmap 的輸出中,首次使用(即 touch)之前 Reserved 和 Committed 的記憶體都將顯示為 Virtual memory,只有在記憶體頁(通常是4k)首次寫入后,它才會消耗物理記憶體,并出現在 top/pmap 輸出的 “常駐記憶體”(即 RSS)中,
    • 當前NMT輸出的主要問題是,它無法區分已 touch 和未 touch 的 Committed 記憶體,
    • 該 Enhancement 提出可以使用 mincore() [5]來查找 NMT 的 Committed 中 RSS 的部分,mincore() 系統呼叫讓一個行程能夠確定一塊虛擬記憶體區域中的分頁是否駐留在物理記憶體中,mincore()已在JDK-8191369 NMT:增強執行緒堆疊跟蹤中實作,需要將其擴展到所有其他型別的記憶體中(如 Java 堆),
    • 遺憾的是該 Enhancement 至今仍是 Unresolved 狀態,

2.JDK-8191369[6] :

    • 1 中提到的 NMT:增強執行緒堆疊跟蹤,使用 mincore() 來追蹤駐留在物理記憶體中的執行緒堆疊的大小,用以解決執行緒堆疊追蹤時有時會夸大記憶體使用情況的痛點,
    • 該 Enhancement 已經在 JDK11 中實作,

參考

 

  1. https://weread.qq.com/web/reader/53032310717f44515302749k37632cd021737693cfc7149
  2. http://openjdk.java.net/jeps/346
  3. https://gitee.com/openeuler/bishengjdk-8/wikis/G1GC????-?????????1??§???????sort_id=3340035
  4. https://bugs.openjdk.org/browse/JDK-8249666
  5. https://man7.org/linux/man-pages/man2/mincore.2.html
  6. https://bugs.openjdk.org/browse/JDK-8191369

 

點擊關注,第一時間了解華為云新鮮技術~

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/514229.html

標籤:其他

上一篇:Java注解(1):碼農的小秘

下一篇:SpringBoot(三) - Slf4j+logback 日志,異步請求,定時任務

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more