1. 程式人生 > >[轉]JVM內存模型

[轉]JVM內存模型

支持 const 系統內存 進程啟動 gc算法 符號 語言 nat 緩沖

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

前情提要–JVM內存模型

開始逐步對堆外內存進行排查,首先了解一下JVM內存模型。根據JVM規範,JVM運行時數據區共分為虛擬機棧、堆、方法區、程序計數器、本地方法棧五個部分。
技術分享圖片?

  • 虛擬機棧:每個線程有一個私有的棧,隨著線程的創建而創建。棧裏面存著的是一種叫“棧幀”的東西,每個方法會創建一個棧幀,棧幀中存放了局部變量表(基本數據類型和對象引用)、操作數棧、方法出口等信息。棧的大小可以固定也可以動態擴展。當棧調用深度大於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

[轉]JVM內存模型