macOS上使用gperftools定位Java内存泄漏问题及解决方案
作者:ponlanby 发布时间:2023-03-02 11:42:38
这几天在排查一个堆外内存泄漏的问题时看到很多人都提到了gperftools这个神器,想要尝试一下结果发现它对macOS的支持不太友好。而且大多数教程是针对C++的,里面的一通编译链接的操作看得我个Java仔眼花缭乱的。所以我在这里整理一份mac和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)
第一列是使用的Direct Memory
第四列是进程以及所有被它调用的方法所占用的总内存
第二列和第五列分别是第一列和第四列的内存占进程总内存的百分比
第三列是第二列数据的一个累加
由于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
官方文档
来源:https://juejin.im/post/5ef98f3d5188252e98363336
猜你喜欢
- 使用类的全权名: System.Text.StringBuilder sb = new System.Text.StringBuilder(
- 本文实例为大家分享了java实现学生成绩档案管理系统的具体代码,供大家参考,具体内容如下实验要求• 学生信息录入,信息包括学号、姓名、专业、
- 上两片第归算法学习:1)递归算法之分而治之策略2)递归算法之归并排序上一篇学习中介绍了了递归算法在排序中的一个应用:归并排序,在排序算法中还
- 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进
- Spring security 重写Filter实现json登录在使用SpringSecurity中,大伙都知道默认的登录数据是通过key/
- 前言第二步理论上我们该写客户端了,但是,在此之前,需要先介绍下一些必要的方法以及操作。写代码还是要尽量的保证通用性,以便以后需要的时候可以拿
- 本文实例讲述了java简单列出文件夹下所有文件的方法。分享给大家供大家参考,具体如下:import Java.io.*;public cla
- 环境介绍 IDEA我用的是2020.2Gradle 安装参考 Gradle安装配置我这安装的是6.6.1C:\Users\herion>
- 一、排序1.获取DataTable的默认视图2.对视图设置排序表达式3.用排序后的视图导出的新DataTable替换就DataTable(A
- C语言字符串大小比较#include <stdio.h>#include <string.h>int fun(cha
- 下标到指针之间和转换以下的程序做了什么。#include <stdio.h> int main() { int a
- 前言在开发过程中,使用模板引擎是很有必要的。jsp已经明显跟不上时代发展了,freemarker用的够够的?换thymeleaf试试吧。sp
- 1.关于JSR-303JSR-303规范(Bean Validation规范)提供了对 Java EE 和 Java SE 中的 Java
- 值传递当调用方法进行值传递时,方法内部会产生一个局部变量,在方法内部使用局部变量的值,并不影响传入原来数据的值,包括在使用基本数据类型的包装
- 说明: 操作系统:deepin20.1一、下载eclipse_2021-03下载jdk-16.0.1下载,选下图所示: 二、安装2
- Java 异常的栈轨迹(Stack Trace)详解 捕获到异常时,往往需要进行一些处理。比较简单直接的
- 反转数组输出前言:此方法来自B站UP主问题:将数字1,2,3,4,5 反向输出 5,4,3,2,1输入参数:1,2,3,4,5代码示例pub
- Monitor对象1.Monitor.Enter(object)方法是获取锁,Monitor.Exit(object)方法是释放锁,这就是M
- 很久没写文章了,一方面是最近几个月比较忙,没太多时间,另一方面是最近拖延症严重,写文章的想法总是一拖再拖。今天找一个小案例写一下,与懒惰对抗
- 本文主要关注如何使用mybatis/mybatis plus连接SQL Server数据库,因此将省略其他项目配置、代码。框架选择应用框架: