您好,登錄后才能下訂單哦!
這篇文章主要介紹關于macOS上使用gperftools定位Java內存泄漏的案例分析,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
一、簡介
gperftools是google提供的一套分析工具,包括堆內存檢測heap-profiler,內存泄漏分析工具heap-checker和CPU性能監測工具cpu-profiler。眾所周知堆外內存的泄漏是很難追蹤的,使用MAT等dump分析工具也只能從堆中最大或者最多的對象入手去分析發生泄漏的地方。而gperftools將malloc的調用替換為它自己的tcmalloc,從而統計所有內存分配的行為,幫助我們更快的定位到發生泄漏的地方。
二、安裝
直接用homebrew安裝就可以了。
brew install gperftools
三、使用gperftools定位內存泄漏
1.示例程序
我們使用下面這段代碼來模擬一個Native Memory泄漏的場景,這段代碼使用native方法分配內存并且默認使用SoftReference持有其引用,因此如果有大量對象存活在堆中又沒有觸發Full GC的話就會導致他們持有的Native Memory一直不被釋放,最終耗盡物理機的內存。
代碼地址
public class NativeMemoryLeakDemo { public static void main(String[] args) throws IOException, FontFormatException { while (true) { test(); } } private static void test() throws IOException, FontFormatException { Resource resource = new ClassPathResource("font/font.ttf"); Font rawFont = Font.createFont(Font.TRUETYPE_FONT, resource.getFile()); Font usedFont = rawFont.deriveFont(Font.PLAIN, 30); BufferedImage bufferedImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = bufferedImage.createGraphics(); g2.setFont(usedFont); g2.drawString("hello world", 16, 35); } }
我們先使用如下的VM參數運行一段時間(Java8)
-XX:CMSInitiatingOccupancyFraction=80
-XX:CompressedClassSpaceSize=528482304
-XX:InitialHeapSize=3221225472
-XX:MaxDirectMemorySize=536870912
-XX:MaxHeapSize=3221225472
-XX:MaxMetaspaceSize=536870912
-XX:MaxNewSize=1157627904
-XX:MetaspaceSize=536870912
-XX:NewSize=1157627904
-XX:SurvivorRatio=8
從圖中可以看到進程占用的內存遠遠大于我們所配的,很明顯這里發生了內存泄漏。那么我們就來看看怎么使用gperftools提供的heap-profiler工具定位到是哪里發生的內存泄漏。
2.使用heap_profiler定位內存泄漏的位置
1) 使用tcmalloc替換malloc
打開bash_profile
vi ~/.bash_profile
指定tcmalloc庫的路徑并將其加入PATH中
export DYLD_INSERT_LIBRARIES=<gperftools_lib_path>/lib/libtcmalloc_and_profiler.dylib
其中<gperftools_lib_path>是gperftools在機器上的安裝位置,例如我是用homebrew安裝在/usr/local/Cellar/gperftools/2.7/下的,那我的路徑就是
export DYLD_INSERT_LIBRARIES=/usr/local/Cellar/gperftools/2.7/lib/libtcmalloc_and_profiler.dylib
保存并生效配置(需要重啟IDE)
source ~/.bash_profile
注:這里替換掉malloc并不會運行heap-profiler,然而由于添加環境變量之后任何人都可以啟動heap-profiler,因此Google不建議在生產環境配置。
2) 監控內存分配
在Idea里導入或創建我們的示例程序,在運行設置里添加heap-profiler運行的環境變量
HEAPPROFILE=<heap_output_path>
<heap_output_path>是heap文件的輸出地址。例如要將結果輸出到tmp文件夾下的memTrack文件中,就是
HEAPPROFILE=/tmp/memTrack
運行程序,可以在日志中看到heap-profiler開始跟蹤內存分配,默認的采樣速率是每分配100M。
在/tmp目錄下也可以看到heap-profiler輸出的日志。
3) 分析輸出
heap-profiler使用pprof將結果轉換成多種格式,這里分別介紹下txt和pdf的輸出
輸出txt
選取最后一次的采樣記錄memTrack.0026.heap,將其轉換成txt文件后輸出到~/HeapFile文件夾下
pprof $JAVA_HOME/bin/java --text /tmp/memTrack.0026.heap > ~/HeapFile/memTrack.txt
結果比較大,這里截取Java部分的輸出結果
Total: 2544.9 MB
2541.9 99.9% 99.9% 2541.9 99.9% 0x00007fff6f5bb1bd
0.0 0.0% 100.0% 298.4 11.7% _JavaMain
0.0 0.0% 100.0% 0.0 0.0% _Java_com_apple_eawt_Application_nativeInitializeApplicationDelegate
0.0 0.0% 100.0% 0.0 0.0% _Java_java_awt_image_BufferedImage_initIDs
0.0 0.0% 100.0% 0.0 0.0% _Java_java_awt_image_ColorModel_initIDs
0.0 0.0% 100.0% 0.0 0.0% _Java_java_awt_image_Raster_initIDs
0.0 0.0% 100.0% 0.0 0.0% _Java_java_awt_image_SampleModel_initIDs
0.0 0.0% 100.0% 0.0 0.0% _Java_java_io_UnixFileSystem_checkAccess
0.0 0.0% 100.0% 0.1 0.0% _Java_java_io_UnixFileSystem_getBooleanAttributes0
0.0 0.0% 100.0% 0.3 0.0% _Java_java_lang_ClassLoader_00024NativeLibrary_load
0.0 0.0% 100.0% 0.1 0.0% _Java_java_lang_ClassLoader_defineClass1
0.0 0.0% 100.0% 0.1 0.0% _Java_java_lang_ClassLoader_findBootstrapClass
0.0 0.0% 100.0% 0.0 0.0% _Java_java_lang_Class_forName0
0.0 0.0% 100.0% 0.2 0.0% _Java_java_lang_System_initProperties
0.0 0.0% 100.0% 0.0 0.0% _Java_java_net_Inet6Address_init
0.0 0.0% 100.0% 0.0 0.0% _Java_java_net_NetworkInterface_init
0.0 0.0% 100.0% 0.0 0.0% _Java_java_net_PlainSocketImpl_initProto
0.0 0.0% 100.0% 0.0 0.0% _Java_java_net_PlainSocketImpl_socketConnect
0.0 0.0% 100.0% 0.9 0.0% _Java_java_util_zip_Inflater_inflateBytes
0.0 0.0% 100.0% 0.2 0.0% _Java_java_util_zip_Inflater_init
0.0 0.0% 100.0% 0.0 0.0% _Java_java_util_zip_ZipFile_getEntry
0.0 0.0% 100.0% 0.4 0.0% _Java_java_util_zip_ZipFile_open
0.0 0.0% 100.0% 0.0 0.0% _Java_sun_awt_CGraphicsEnvironment_registerDisplayReconfiguration
0.0 0.0% 100.0% 0.5 0.0% _Java_sun_awt_image_BufImgSurfaceData_initRaster
0.0 0.0% 100.0% 0.1 0.0% _Java_sun_font_CFontManager_loadNativeDirFonts
0.0 0.0% 100.0% 0.0 0.0% _Java_sun_font_StrikeCache_freeIntMemory
0.0 0.0% 100.0% 0.4 0.0% _Java_sun_font_T2KFontScaler_createScalerContextNative
0.0 0.0% 100.0% 764.7 30.0% _Java_sun_font_T2KFontScaler_getGlyphImageNative
0.0 0.0% 100.0% 0.0 0.0% _Java_sun_font_T2KFontScaler_initIDs
0.0 0.0% 100.0% 1751.7 68.8% _Java_sun_font_T2KFontScaler_initNativeScaler
0.0 0.0% 100.0% 0.0 0.0% _Java_sun_java2d_SurfaceData_initIDs
0.0 0.0% 100.0% 0.0 0.0% _Java_sun_java2d_loops_GraphicsPrimitiveMgr_initIDs
0.0 0.0% 100.0% 0.4 0.0% _Java_sun_java2d_opengl_CGLGraphicsConfig_getOGLCapabilities
0.0 0.0% 100.0% 0.0 0.0% _Java_sun_java2d_opengl_OGLRenderQueue_flushBuffer
可以看到第一行是整個程序占用的總內存,后面按照調用棧的順序記錄了每個方法的內存使用情況(單位: MB)
由于gperftools是C++下的工具,可以看到在Java下無法得到完整的監控信息。但是我們仍然可以通過第四列找到 _Java_sun_font_T2KFontScaler_initNativeScaler 這個方法占用了最多的內存,查看代碼可以看到這個方法是被native關鍵字修飾的,說明很可能這里分配的內存沒有被JVM回收。去搜索一下就能查到確實是這里分配的內存被Font2D對象持有最終造成了泄漏。
輸出pdf
pprof還支持將統計結果圖形化輸出到pdf,方便我們更直觀的找到占用最多內存的地方。這里同樣用memTrack.0026.heap,將其轉換成pdf格式后輸出到~/HeapFile文件夾下
pprof $JAVA_HOME/bin/java --pdf /tmp/memTrack.0026.heap > ~/HeapFile/memTrack.pdf
之后就可以在~/HeapFile下看到生成的pdf文件了。圖片比較大,這里也只截取一部分。
從圖上可以看到內存分配的調用棧被轉化為多條調用鏈路,最終都指向AllocMem進行內存分配,并且內存占比高的鏈路還被貼心的加粗。
注:如果輸出pdf的時候碰到以下錯誤,則需要安裝對應的依賴
dot: not found 需要安裝graphviz brew install graphviz ps2pdf: command not found 需要安裝ghostscript brew install ghostscript
以上是關于macOS上使用gperftools定位Java內存泄漏的案例分析的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。