1. 程式人生 > >[轉]JVM記憶體模型

[轉]JVM記憶體模型

最近排查一個線上java服務常駐記憶體異常高的問題,大概現象是:java堆Xmx配置了8G,但執行一段時間後常駐記憶體RES從5G逐漸增長到13G #補圖#,導致機器開始swap從而服務整體變慢。
由於Xmx只配置了8G但RES常駐記憶體達到了13G,多出了5G堆外記憶體,經驗上判斷這裡超出太多不太正常。

前情提要–JVM記憶體模型

開始逐步對堆外記憶體進行排查,首先了解一下JVM記憶體模型。根據JVM規範,JVM執行時資料區共分為虛擬機器棧、堆、方法區、程式計數器、本地方法棧五個部分。
o_dc695f48-4189-4fc7-b950-ed25f6c1521708518830

  • 虛擬機器棧:每個執行緒有一個私有的棧,隨著執行緒的建立而建立。棧裡面存著的是一種叫“棧幀”的東西,每個方法會建立一個棧幀,棧幀中存放了區域性變量表(基本資料型別和物件引用)、運算元棧、方法出口等資訊。棧的大小可以固定也可以動態擴充套件。當棧呼叫深度大於JVM所允許的範圍,會丟擲StackOverflowError的錯誤,不過這個深度範圍不是一個恆定的值。虛擬機器棧除了上述錯誤外,還有另一種錯誤,那就是當申請不到空間時,會丟擲 OutOfMemoryError。
  • 本地方法棧:與虛擬機器棧類似,區別是虛擬機器棧執行java方法,本地方法站執行native方法。在虛擬機器規範中對本地方法棧中方法使用的語言、使用方法與資料結構沒有強制規定,因此虛擬機器可以自由實現它。本地方法棧也可以丟擲StackOverflowError和OutOfMemoryError。
  • PC 暫存器,也叫程式計數器。可以看成是當前執行緒所執行的位元組碼的行號指示器。在任何一個確定的時刻,一個處理器(對於多核心來說是一個核心)都只會執行一條執行緒中的指令。因此,為了執行緒切換後能恢復到正確的執行位置,每條執行緒都需要一個獨立的程式計數器,我們稱這類記憶體區域為“執行緒私有”記憶體。倘若當前執行緒執行的是 JAVA 的方法,則該暫存器中儲存當前執行指令的地址;倘若執行的是native 方法,則PC暫存器中為空。
  • 堆記憶體。堆記憶體是 JVM 所有執行緒共享的部分,在虛擬機器啟動的時候就已經建立。所有的物件和陣列都在堆上進行分配。這部分空間可通過 GC 進行回收。當申請不到空間時會丟擲 OutOfMemoryError。
  • 方法區也是所有執行緒共享。主要用於儲存類的資訊、常量池、靜態變數、及時編譯器編譯後的程式碼等資料。方法區邏輯上屬於堆的一部分,但是為了與堆進行區分,通常又叫“非堆”。

前情提要–PermGen(永久代)和 Metaspace(元空間)

PermGen space 和 Metaspace是HotSpot對於方法區的不同實現。在Java虛擬機器(以下簡稱JVM)中,類包含其對應的元資料,比如類名,父類名,類的型別,訪問修飾符,欄位資訊,方法資訊,靜態變數,常量,類載入器的引用,類的引用。在HotSpot JDK 1.8之前這些類元資料資訊存放在一個叫永久代的區域(PermGen space),永久代一段連續的記憶體空間。在JDK 1.8開始,方法區實現採用Metaspace代替,這些元資料資訊直接使用本地記憶體來分配。元空間與永久代之間最大的區別在於:元空間並不在虛擬機器中,而是使用本地記憶體。

堆外記憶體

java 8下是指除了Xmx設定的java堆(java 8以下版本還包括MaxPermSize設定的持久代大小)外,java程序使用的其他記憶體。主要包括:DirectByteBuffer分配的記憶體,JNI裡分配的記憶體,執行緒棧分配佔用的系統記憶體,jvm本身執行過程分配的記憶體,codeCache,java 8裡還包括metaspace元資料空間。

分析java堆

由於現象是RES比較高,先看一下java堆是否有異常。把java堆dump下來仔細排查一下,jmap -histo:live pid,發現整個堆回收完也才幾百兆,遠不到8G的Xmx的上限值,GC日誌看著也沒啥異常。基本排查java堆記憶體洩露的可能性。

分析DirectByteBuffer的佔用

DirectByteBuffer簡單瞭解

由於服務使用的RPC框架底層採用了Netty等NIO框架,會使用到DirectByteBuffer這種“冰山物件”,先簡單排查一下。關於DirectByteBuffer先介紹一下:JDK 1.5之後ByteBuffer類提供allocateDirect(int capacity)進行堆外記憶體的申請,底層通過unsafe.allocateMemory(size)實現,會呼叫malloc方法進行記憶體分配。實際上,在java堆裡是維護了一個記錄堆外地址和大小的DirectByteBuffer的物件,所以GC是能通過操作DirectByteBuffer物件來間接操作對應的堆外記憶體,從而達到釋放堆外記憶體的目的。但如果一旦這個DirectByteBuffer物件熬過了young GC到達了Old區,同時Old區一直又沒做CMS GC或者Full GC的話,這些“冰山物件”會將系統實體記憶體慢慢消耗掉。對於這種情況JVM留了後手,Bits給DirectByteBuffer前首先需要向Bits類申請額度,Bits類維護了一個全域性的totalCapacity變數,記錄著全部DirectByteBuffer的總大小,每次申請,都先看看是否超限(堆外記憶體的限額預設與堆內記憶體Xmx設定相仿),如果已經超限,會主動執行Sytem.gc(),System.gc()會對新生代的老生代都會進行記憶體回收,這樣會比較徹底地回收DirectByteBuffer物件以及他們關聯的堆外記憶體。但如果啟動時通過-DisableExplicitGC禁止了System.gc(),那麼這裡就會出現比較嚴重的問題,導致回收不了DirectByteBuffer底下的堆外記憶體了。所以在類似Netty的框架裡對DirectByteBuffer是框架自己主動回收來避免這個問題。

DirectByteBuffer為什麼要用堆外記憶體

DirectByteBuffer是直接通過native方法使用malloc分配記憶體,這塊記憶體位於java堆之外,對GC沒有影響;其次,在通訊場景下,堆外記憶體能減少IO時的記憶體複製,不需要堆記憶體Buffer拷貝一份到直接記憶體中,然後才寫入Socket中。所以DirectByteBuffer一般用於通訊過程中作為緩衝池來減少記憶體拷貝。當然,由於直接用malloc在OS裡申請一段記憶體,比在已申請好的JVM堆內記憶體裡劃一塊出來要慢,所以在Netty中一般用池化的 PooledDirectByteBuf 對DirectByteBuffer進行重用進一步提升效能。

如何排查DirectByteBuffer的使用情況

JMX提供了監控direct buffer的MXBean,啟動服務時開啟-Dcom.sun.management.jmxremote.port=9527 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=10.79.40.147,JMC掛上後執行一段時間,此時Xmx是8G的情況下整體RES逐漸增長到13G,MBean裡找到java.nio.BufferPool下的direct節點,檢視direct buffer的情況,發現總共才213M。為了進一步排除,在啟動時通過-XX:MaxDirectMemorySize來限制DirectByteBuffer的最大限額,調整為1G後,程序整體常駐記憶體的增長並沒有限制住,因此這裡基本排除了DirectByteBuffer的嫌疑。

使用NMT排查JVM原生記憶體使用

Native Memory Tracking(NMT)使用

NMT是Java7U40引入的HotSpot新特性,可用於監控JVM原生記憶體的使用,但比較可惜的是,目前的NMT不能監控到JVM之外或原生庫分配的記憶體。java程序啟動時指定開啟NMT(有一定的效能損耗),輸出級別可以設定為“summary”或“detail”級別。如:

-XX:NativeMemoryTracking=summary 或者 -XX:NativeMemoryTracking=detail

開啟後,通過jcmd可以訪問收集到的資料。

jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff 

如:jcmd 11 VM.native_memory,輸出如下:

Native Memory Tracking:

Total: reserved=12259645KB(保留記憶體), committed=11036265KB (提交記憶體)
堆記憶體使用情況,保留記憶體和提交記憶體和Xms、Xmx一致,都是8G。
-                 Java Heap (reserved=8388608KB, committed=8388608KB) (mmap: reserved=8388608KB, committed=8388608KB) 用於儲存類元資料資訊使用到的原生記憶體,總共12045個類,整體實際使用了79M記憶體。 - Class (reserved=1119963KB, committed=79751KB) (classes #12045) (malloc=1755KB #29277) (mmap: reserved=1118208KB, committed=77996KB) 總共2064個執行緒,提交記憶體是2.1G左右,一個執行緒1M,和設定Xss1m相符。 - Thread (reserved=2130294KB, committed=2130294KB) (thread #2064) (stack: reserved=2120764KB, committed=2120764KB) (malloc=6824KB #10341) (arena=2706KB #4127) JIT的程式碼快取,12045個類JIT編譯後代碼快取整體使用79M記憶體。 - Code (reserved=263071KB, committed=79903KB) (malloc=13471KB #15191) (mmap: reserved=249600KB, committed=66432KB) GC相關使用到的一些堆外記憶體,比如GC演算法的處理鎖會使用一些堆外空間。118M左右。 - GC (reserved=118432KB, committed=118432KB) (malloc=93848KB #453) (mmap: reserved=24584KB, committed=24584KB) JAVA編譯器自身操作使用到的一些堆外記憶體,很少。 - Compiler (reserved=975KB, committed=975KB) (malloc=844KB #1074) (arena=131KB #3) Internal:memory used by the command line parser, JVMTI, properties等。 - Internal (reserved=117158KB, committed=117158KB) (malloc=117126KB #44857) (mmap: reserved=32KB, committed=32KB) Symbol:保留字串(Interned String)的引用與符號表引用放在這裡,17M左右 - Symbol (reserved=17133KB, committed=17133KB) (malloc=13354KB #145640) (arena=3780KB #1) NMT本身佔用的堆外記憶體,4M左右 - Native Memory Tracking (reserved=4402KB, committed=4402KB) (malloc=396KB #5287) (tracking overhead=4006KB) 不知道啥,用的很少。 - Arena Chunk (reserved=272KB, committed=272KB) (malloc=272KB) 其他未分類的堆外記憶體佔用,100M左右。 - Unknown (reserved=99336KB, committed=99336KB) (mmap: reserved=99336KB, committed=99336KB) 
  • 保留記憶體(reserved):reserved memory 是指JVM 通過mmaped PROT_NONE 申請的虛擬地址空間,在頁表中已經存在了記錄(entries),保證了其他程序不會被佔用,且保證了邏輯地址的連續性,能簡化指標運算。

  • 提交記憶體(commited):committed memory 是JVM向操做系統實際分配的記憶體(malloc/mmap),mmaped PROT_READ | PROT_WRITE,仍然會page faults,但是跟 reserved 不同,完全核心處理像什麼也沒發生一樣。

這裡需要注意的是:由於malloc/mmap的lazy allocation and paging機制,即使是commited的記憶體,也不一定會真正分配實體記憶體。

malloc/mmap is lazy unless told otherwise. Pages are only backed by physical memory once they're accessed.

Tips:由於記憶體是一直在緩慢增長,因此在使用NMT跟蹤堆外記憶體時,一個比較好的辦法是,先建立一個記憶體使用基線,一段時間後再用當時資料和基線進行差別比較,這樣比較容易定位問題。

jcmd 11 VM.native_memory baseline

同時pmap看一下實體記憶體的分配,RSS佔用了10G。

pmap -x 11 | sort -n -k3

執行一段時間後,做一下summary級別的diff,看下記憶體變化,同時再次pmap看下RSS增長情況。

jcmd 11 VM.native_memory summary.diff
Native Memory Tracking:

Total: reserved=13089769KB +112323KB, committed=11877285KB +117915KB

-                 Java Heap (reserved=8388608KB, committed=8388608KB)
                            (mmap: reserved=8388608KB, committed=8388608KB)

-                     Class (reserved=1126527KB +2161KB, committed=85771KB +2033KB)
                            (classes #12682 +154)
                            (malloc=2175KB +113KB #37289 +2205)
                            (mmap: reserved=1124352KB +2048KB, committed=83596KB +1920KB)

-                    Thread (reserved=2861485KB +94989KB, committed=2861485KB +94989KB)
                            (thread #2772 +92) (stack: reserved=2848588KB +94576KB, committed=2848588KB +94576KB) (malloc=9169KB +305KB #13881 +460) (arena=3728KB +108 #5543 +184) - Code (reserved=265858KB +1146KB, committed=94130KB +6866KB) (malloc=16258KB +1146KB #18187 +1146) (mmap: reserved=249600KB, committed=77872KB +5720KB) - GC (reserved=118433KB +1KB, committed=118433KB +1KB) (malloc=93849KB +1KB #487 +24) (mmap: reserved=24584KB, committed=24584KB) - Compiler (reserved=1956KB +253KB, committed=1956KB +253KB) (malloc=1826KB +253KB #2098 +271) (arena=131KB #3) - Internal (reserved=203932KB +13143KB, committed=203932KB +13143KB) (malloc=203900KB +13143KB #62342 +3942) (mmap: reserved=32KB, committed=32KB) - Symbol (reserved=17820KB +108KB, committed=17820KB +108KB) (malloc=13977KB +76KB #152204 +257) (arena=3844KB +32 #1) - Native Memory Tracking (reserved=5519KB +517KB, committed=5519KB +517KB) (malloc=797KB +325KB #9992 +3789) (tracking overhead=4722KB +192KB) - Arena Chunk (reserved=294KB +5KB, committed=294KB +5KB) (malloc=294KB +5KB) - Unknown (reserved=99336KB, committed=99336KB) (mmap: reserved=99336KB, committed=99336KB 

發現這段時間pmap看到的RSS增長了3G多,但NMT觀察到的記憶體增長了不到120M,還有大概2G多常駐記憶體不知去向,因此也基本排除了由於JVM自身管理的堆外記憶體的嫌疑。

排查Metaspace元空間的堆外記憶體佔用

由於線上使用的是JDK8,前面提到,JDK8裡的元空間實際上使用的也是堆外記憶體,預設沒有設定元空間大小的情況下,元空間最大堆外記憶體大小和Xmx是一致的。JMC連上後看下記憶體tab下metaspace一欄的記憶體佔用情況,發現元空間只佔用不到80M記憶體,也排除了它的可能性。實在不放心的話可以通過-XX:MaxMetaspaceSize設定元空間使用堆外記憶體的上限。

gdb分析記憶體塊內容

上面提到使用pmap來檢視程序的記憶體對映,pmap命令實際是讀取了/proc/pid/maps和/porc/pid/smaps檔案來輸出。發現一個細節,pmap取出的記憶體對映發現很多64M大小的記憶體塊。這種記憶體塊逐漸變多且佔用的RSS常駐記憶體也逐漸增長到reserved保留記憶體大小,記憶體增長的2G多基本上也是由於這些64M的記憶體塊導致的,因此看一下這些記憶體塊裡具體內容。

strace掛上監控下記憶體分配和回收的系統呼叫:
strace -o /data1/weibo/logs/strace_output2.txt -T -tt -e mmap,munmap,mprotect -fp 12

看記憶體申請和釋放的情況:

cat ../logs/strace_output2.txt | grep mprotect | grep -v resumed | awk '{print int($4)}' | sort -rn | head -5 cat ../logs/strace_output2.txt | grep mmap | grep -v resumed | awk '{print int($4)}' | sort -rn | head -5 cat ../logs/strace_output2.txt | grep munmap | grep -v resumed | awk '{print int($4)}' | sort -rn | head -5 

配合pmap -x 10看一下實際記憶體分配情況:

找一塊記憶體塊進行dump:

gdb --batch --pid 11 -ex "dump memory a.dump 0x7fd488000000 0x7fd488000000+56124000"

簡單分析一下內容,發現絕大部分是亂碼的二進位制內容,看不出什麼問題。
strings a.dump | less
或者: hexdump -C a.dump | less
或者: view a.dump

沒啥思路的時候,隨便搜了一下發現貌似很多人碰到這種64M記憶體塊的問題(比如這裡),瞭解到glibc的記憶體分配策略在高版本有較大調整:

«從glibc 2.11(為應用系統在多核心CPU和多Sockets環境中高伸縮性提供了一個動態記憶體分配的特性增強)版本開始引入了per thread arena記憶體池,Native Heap區被打散為sub-pools ,這部分記憶體池叫做Arena記憶體池。也就是說,以前只有一個main arena,目前是一個main arena(還是位於Native Heap區) + 多個per thread arena,多個執行緒之間不再共用一個arena記憶體區域了,保證每個執行緒都有一個堆,這樣避免記憶體分配時需要額外的鎖來降低效能。main arena主要通過brk/sbrk系統呼叫去管理,per thread arena主要通過mmap系統呼叫去分配和管理。»

«一個32位的應用程式程序,最大可建立 2 CPU總核數個arena記憶體池(MALLOC_ARENA_MAX),每個arena記憶體池大小為1MB,一個64位的應用程式程序,最大可建立 8 CPU總核數個arena記憶體池(MALLOC_ARENA_MAX),每個arena記憶體池大小為64MB»

ptmalloc2記憶體分配和釋放

«當某一執行緒需要呼叫 malloc()分配記憶體空間時, 該執行緒先檢視執行緒私有變數中是否已經存在一個分配區,如果存在, 嘗試對該分配區加鎖,如果加鎖成功,使用該分配區分配記憶體,如果失敗, 該執行緒搜尋迴圈連結串列試圖獲得一個沒有加鎖的分配區。如果所有的分配區都已經加鎖,那麼 malloc()會開闢一個新的分配區,把該分配區加入到全域性分配區迴圈連結串列並加鎖,然後使用該分配區進行分配記憶體操作。在釋放操作中,執行緒同樣試圖獲得待釋放記憶體塊所在分配區的鎖,如果該分配區正在被別的執行緒使用,則需要等待直到其他執行緒釋放該分配區的互斥鎖之後才可以進行釋放操作。使用者 free 掉的記憶體並不是都會馬上歸還給系統,ptmalloc2 會統一管理 heap 和 mmap 對映區域中的空閒的chunk,當用戶進行下一次分配請求時, ptmalloc2 會首先試圖在空閒的chunk 中挑選一塊給使用者,這樣就避免了頻繁的系統呼叫,降低了記憶體分配的開銷。»

ptmalloc2的記憶體收縮機制

«業務層呼叫free方法釋放記憶體時,ptmalloc2先判斷 top chunk 的大小是否大於 mmap 收縮閾值(預設為 128KB),如果是的話,對於主分配區,則會試圖歸還 top chunk 中的一部分給作業系統。但是最先分配的 128KB 空間是不會歸還的,ptmalloc 會一直管理這部分記憶體,用於響應使用者的分配 請求;如果為非主分配區,會進行 sub-heap 收縮,將 top chunk 的一部分返回給操 作系統,如果 top chunk 為整個 sub-heap,會把整個 sub-heap 還回給作業系統。做 完這一步之後,釋放結束,從 free() 函式退出。可以看出,收縮堆的條件是當前 free 的 chunk 大小加上前後能合併 chunk 的大小大於 64k,並且要 top chunk 的大 小要達到 mmap 收縮閾值,才有可能收縮堆。»

ptmalloc2的mmap分配閾值動態調整

«M_MMAP_THRESHOLD 用於設定 mmap 分配閾值,預設值為 128KB,ptmalloc 預設開啟 動態調整 mmap 分配閾值和 mmap 收縮閾值。當用戶需要分配的記憶體大於 mmap 分配閾值,ptmalloc 的 malloc()函式其實相當於 mmap() 的簡單封裝,free 函式相當於 munmap()的簡單封裝。相當於直接通過系統呼叫分配記憶體, 回收的記憶體就直接返回給作業系統了。因為這些大塊記憶體不能被 ptmalloc 快取管理,不能重用,所以 ptmalloc 也只有在萬不得已的情況下才使用該方式分配記憶體。»

業務特性和ptmalloc2記憶體分配的gap

當前業務併發較大,執行緒較多,記憶體申請時容易造成鎖衝突申請多個arena,另外該服務涉及到圖片的上傳和處理,底層會比較頻繁的通過JNI呼叫ImageIO的圖片讀取方法(com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage),經常會向glibc申請10M以上的buffer記憶體,考慮到ptmalloc2的lazy回收機制和mmap分配閾值動態調整預設開啟,對於這些申請的大記憶體塊,使用完後仍然會停留在arena中不會歸還,同時也比較難得到收縮的機會去釋放(當前回收的chunk和top chunk相鄰,且合併後大於64K)。因此在這種較高併發的多執行緒業務場景下,RES的增長也是不可避免。

如何優化解決
三種方案:

第一種:控制分配區的總數上限。預設64位系統分配區數為:cpu核數*8,如當前環境16核系統分配區數為128個,每個64M上限的話最多可達8G,限制上限後,後續不夠的申請會直接走mmap分配和munmap回收,不會進入ptmalloc2的buffer池。
所以第一種方案調整一下分配池上限個數到4:

export MALLOC_ARENA_MAX=4

第二種:之前降到ptmalloc2預設會動態調整mmap分配閾值,因此對於較大的記憶體請求也會進入ptmalloc2的記憶體buffer池裡,這裡可以去掉ptmalloc的動態調整功能。可以設定 M_TRIM_THRESHOLD,M_MMAP_THRESHOLD,M_TOP_PAD 和 M_MMAP_MAX 中的任意一個。這裡可以固定分配閾值為128K,這樣超過128K的記憶體分配請求都不會進入ptmalloc的buffer池而是直接走mmap分配和munmap回收(效能上會有損耗,當前環境大概10%)。:

export MALLOC_MMAP_THRESHOLD_=131072
export MALLOC_TRIM_THRESHOLD_=131072
export MALLOC_TOP_PAD_=131072 export MALLOC_MMAP_MAX_=65536 

第三種:使用tcmalloc來替代預設的ptmalloc2。google的tcmalloc提供更優的記憶體分配效率,效能更好,ThreadCache會階段性的回收記憶體到CentralCache裡。 解決了ptmalloc2中arena之間不能遷移導致記憶體浪費的問題。

tcmalloc安裝使用
1.實現原理

perf-tools實現原理是:在java應用程式執行時,當系統分配記憶體時呼叫malloc時換用它的libtcmalloc.so,也就是TCMalloc會自動替換掉glibc預設的malloc和free,這樣就能做一些統計。使用TCMalloc(Thread-Caching Malloc)與標準的glibc庫的malloc相比,TCMalloc在記憶體的分配上效率和速度要高,==瞭解更多TCMalloc

2. 安裝和使用
2.1 前置工具的安裝
yum -y install gcc make
yum -y install gcc gcc-c++
yum -y perl
2.2 libunwind

使用perf-tools的TCMalloc,在64bit系統上需要先安裝libunwind(http://download.savannah.gnu.org/releases/libunwind/libunwind-1.2.tar.gz,只能是這個版本),這個庫為基於64位CPU和作業系統的程式提供了基本的堆疊輾轉開解功能,其中包括用於輸出堆疊跟蹤的API、用於以程式設計方式輾轉開解堆疊的API以及支援C++異常處理機制的API,32bit系統不需安裝。

tar zxvf libunwind-1.2.tar.gz
./configure
make
make install
make clean
2.3 perf-tools

https://github.com/gperftools/gperftools下載相應的google-perftools版本。

tar zxvf google-perftools-2.7.tar.gz
./configure
make
make install
make clean
#修改lc_config,加入/usr/local/lib(libunwind的lib所在目錄)
echo "/usr/local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf #使libunwind生效 ldconfig 
2.3.1 關於etc/ld.so.conf

這個檔案記錄了編譯時使用的動態連結庫的路徑。預設情況下,編譯器只會使用/lib和/usr/lib這兩個目錄下的庫檔案。
如果你安裝了某些庫,比如在安裝gtk+-2.4.13時它會需要glib-2.0 >= 2.4.0,辛苦的安裝好glib後沒有指定 –prefix=/usr 這樣glib庫就裝到了/usr/local下,而又沒有在/etc/ld.so.conf中新增/usr/local/lib。
庫檔案的路徑如 /usr/lib 或 /usr/local/lib 應該在 /etc/ld.so.conf 檔案中,這樣 ldd 才能找到這個庫。在檢查了這一點後,要以 root 的身份執行 /sbin/ldconfig。
將/usr/local/lib加入到/etc/ld.so.conf中,這樣安裝gtk時就會去搜索/usr/local/lib,同樣可以找到需要的庫

2.3.2 關於ldconfig

ldconfig的作用就是將/etc/ld.so.conf列出的路徑下的庫檔案 快取到/etc/ld.so.cache 以供使用
因此當安裝完一些庫檔案,(例如剛安裝好glib),或者修改ld.so.conf增加新的庫路徑後,需要執行一下/sbin/ldconfig
使所有的庫檔案都被快取到ld.so.cache中,如果沒做,即使庫檔案明明就在/usr/lib下的,也是不會被使用的

2.4 為perf-tools新增執行緒目錄
mkdir /data1/weibo/logs/gperftools/tcmalloc/heap
chmod 0777 /data1/weibo/logs/gperftools/tcmalloc/heap
2.5 修改tomcat啟動指令碼

catalina.sh裡新增:

ldconfig
export LD_PRELOAD=/usr/local/lib/libtcmalloc.so
export HEAPPROFILE=/data1/weibo/logs/gperftools/tcmalloc/heap 

修改後重啟tomcat的容器。

2.5.1 關於LD_PRELOAD

LD_PRELOAD是Linux系統的一個環境變數,它可以影響程式的執行時的連結(Runtime linker),它允許你定義在程式執行前優先載入的動態連結庫。這個功能主要就是用來有選擇性的載入不同動態連結庫中的相同函式。通過這個環境變數,我們可以在主程式和其動態連結庫的中間載入別的動態連結庫,甚至覆蓋正常的函式庫。一方面,我們可以以此功能來使用自己的或是更好的函式(無需別人的原始碼),而另一方面,我們也可以以向別人的程式注入程式,從而達到特定的目的。更多關於LD_PRELOAD