文章目录
-
- [0. 概要](#0. 概要)
- [1. 编译jemalloc](#1. 编译jemalloc)
- [2. 编译钩子共享库liballoc_hook.so](#2. 编译钩子共享库liballoc_hook.so)
- [3. 使用`LD_PRELOAD`加载钩子库liballoc_hook.so测试](#3. 使用
LD_PRELOAD
加载钩子库liballoc_hook.so测试) -
- [3.1 设置环境变量](#3.1 设置环境变量)
- [3.2 使用`LD_PRELOAD`加载钩子库并运行程序](#3.2 使用
LD_PRELOAD
加载钩子库并运行程序) - [3.3 发送`SIGUSR1`信号以触发堆栈信息打印](#3.3 发送
SIGUSR1
信号以触发堆栈信息打印) - [3.4 使用jeprof解析heap堆栈信息文件](#3.4 使用jeprof解析heap堆栈信息文件)
- [4. 示例程序example.cpp代码](#4. 示例程序example.cpp代码)
- [5. 注意事项](#5. 注意事项)
- [6. jemalloc的限制](#6. jemalloc的限制)
0. 概要
本文介绍如何结合LD_PRELOAD
与jemalloc
,在接收到SIGUSR1
信号时打印程序的堆栈信息。详细步骤包括编译和配置jemalloc
,编写信号处理程序,并通过LD_PRELOAD
加载共享库的方法。
1. 编译jemalloc
编译并安装启用prof
功能的jemalloc
。以下是Ubuntu 18.04上的编译步骤:
sh
git clone https://github.com/jemalloc/jemalloc.git # 本文测试的版本是jemalloc-5.3.0
cd jemalloc
./configure --prefix=/usr/local --enable-prof CFLAGS="-fPIC"
make -j10
sudo make install
确保编译 libjemalloc.a
时使用了 -fPIC
选项。
2. 编译钩子共享库liballoc_hook.so
创建一个名为alloc_hook.c
的文件,并实现信号处理函数:
c
/*
gcc -o liballoc_hook.so -shared -fPIC alloc_hook.c -Wl,-Bstatic -ljemalloc -Wl,-Bdynamic
*/
#include <jemalloc/jemalloc.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
// 信号处理函数
void handle_signal(int signum) {
if (signum == SIGUSR1) {
// 触发 jemalloc 的 heap profiling dump
mallctl("prof.dump", NULL, NULL, NULL, 0);
printf("Heap profile dump generated.\n");
}
}
// 初始化函数
void __attribute__((constructor)) init_hook() {
// 设置信号处理函数
signal(SIGUSR1, handle_signal);
printf("Signal handler for SIGUSR1 is set.\n");
}
使用以下命令编译liballoc_hook.so
并静态链接libjemalloc.a
:
sh
gcc -o liballoc_hook.so -shared -fPIC alloc_hook.c -Wl,-Bstatic -ljemalloc -Wl,-Bdynamic -lpthread
3. 使用LD_PRELOAD
加载钩子库liballoc_hook.so测试
假设你的目标程序是example
,通过LD_PRELOAD
加载liballoc_hook.so钩子库,按照以下步骤运行和测试:
3.1 设置环境变量
sh
export MALLOC_CONF="prof:true,prof_active:true,lg_prof_sample:0,tcache:false,prof_prefix:jeprof.out"
prof:true
:启用配置文件。prof_active:true
:启用性能分析。lg_prof_sample:0
:设置采样率为最高。tcache:false
:禁用线程缓存,可能影响性能,但在进行性能分析时,可以提供更准确的内存分配数据。prof_prefix:jeprof.out
:指定性能分析输出文件前缀。
3.2 使用LD_PRELOAD
加载钩子库并运行程序
sh
LD_PRELOAD="/path/to/liballoc_hook.so" ./example
3.3 发送SIGUSR1
信号以触发堆栈信息打印
sh
killall -10 example
# 或者
killall -SIGUSR1 example
通过以上步骤,你可以在接收到SIGUSR1
信号时打印jemalloc
的堆栈信息,并将其输出到本地目录。本文得到的堆栈信息文件名为jeprof.out.60571.0.m0.heap
。
3.4 使用jeprof解析heap堆栈信息文件
通过如下命令分析该堆栈信息文件:
sh
jeprof --show_bytes --text --lines ./example ./jeprof.out.60571.0.m0.heap
解析结果示例如下:
sh
$ jeprof --show_bytes --text --lines ./example jeprof.out.60571.0.m0.heap
Using local file ./example.
Using local file jeprof.out.60571.0.m0.heap.
Total: 83512 B
82944 99.3% 99.3% 82944 99.3% prof_backtrace_impl /tmp/jemalloc-5.3.0/src/prof_sys.c:103
448 0.5% 99.9% 448 0.5% allocateIntArray /home/test/jemalloc_test/example.cpp:13
80 0.1% 100.0% 80 0.1% allocateDynamicArray /home/test/jemalloc_test/example.cpp:32 (discriminator 1)
32 0.0% 100.0% 32 0.0% allocateString /home/test/jemalloc_test/example.cpp:25
8 0.0% 100.0% 8 0.0% allocateDouble /home/test/jemalloc_test/example.cpp:19
0 0.0% 100.0% 1024 1.2% _IO_new_file_overflow /build/glibc-2ORdQG/glibc-2.27/libio/fileops.c:759
0 0.0% 100.0% 1024 1.2% _IO_new_file_xsputn /build/glibc-2ORdQG/glibc-2.27/libio/fileops.c:1266
0 0.0% 100.0% 1024 1.2% _IO_puts /build/glibc-2ORdQG/glibc-2.27/libio/ioputs.c:40
0 0.0% 100.0% 1024 1.2% __GI__IO_doallocbuf /build/glibc-2ORdQG/glibc-2.27/libio/genops.c:365
0 0.0% 100.0% 1024 1.2% __GI__IO_file_doallocate /build/glibc-2ORdQG/glibc-2.27/libio/filedoalloc.c:101
0 0.0% 100.0% 568 0.7% __libc_start_main /build/glibc-2ORdQG/glibc-2.27/csu/../csu/libc-start.c:310
0 0.0% 100.0% 82944 99.3% _dl_start_user :?
0 0.0% 100.0% 568 0.7% _start ??:?
0 0.0% 100.0% 448 0.5% allocateMemory /home/test/jemalloc_test/example.cpp:51
0 0.0% 100.0% 8 0.0% allocateMemory /home/test/jemalloc_test/example.cpp:52
0 0.0% 100.0% 32 0.0% allocateMemory /home/test/jemalloc_test/example.cpp:53
0 0.0% 100.0% 80 0.1% allocateMemory /home/test/jemalloc_test/example.cpp:54
0 0.0% 100.0% 82944 99.3% call_init /build/glibc-2ORdQG/glibc-2.27/elf/dl-init.c:72
0 0.0% 100.0% 82944 99.3% imalloc (inline) /tmp/jemalloc-5.3.0/src/jemalloc.c:2694
0 0.0% 100.0% 82944 99.3% imalloc_body (inline) /tmp/jemalloc-5.3.0/src/jemalloc.c:2550
0 0.0% 100.0% 1024 1.2% init_hook ??:?
0 0.0% 100.0% 82944 99.3% je_malloc_default /tmp/jemalloc-5.3.0/src/jemalloc.c:2722
0 0.0% 100.0% 82944 99.3%
je_prof_backtrace /tmp/jemalloc-5.3.0/src/prof_sys.c:284
0 0.0% 100.0% 82944 99.3% je_prof_tctx_create /tmp/jemalloc-5.3.0/src/prof.c:195
0 0.0% 100.0% 568 0.7% main /home/test/jemalloc_test/example.cpp:60
0 0.0% 100.0% 82944 99.3% prof_alloc_prep (inline) /tmp/jemalloc-5.3.0/include/jemalloc/internal/prof_inlines.h:141
0 0.0% 100.0% 81920 98.1% std::__once_callable ??:0
4. 示例程序example.cpp代码
以下是完整的example.cpp
代码,编译方法: g++ -g -o example example.cpp
:
cpp
#include <sys/mman.h> // mmap, munmap
#include <unistd.h> // usleep
#include <csignal> // signal, sigaction
#include <cstdlib> // rand()和srand()
#include <ctime> // time()
#include <iostream>
#include <string>
#include <vector>
// 分配int数组
void allocateIntArray() {
const int* intPtr = new int[100];
std::cout << "Allocated int array at: " << intPtr << std::endl;
}
// 分配double
void allocateDouble() {
const double* doublePtr = new double(3.14);
std::cout << "Allocated double at: " << doublePtr << ", value: " << *doublePtr << std::endl;
}
// 分配字符串
void allocateString() {
const std::string* strPtr = new std::string("Hello, World!");
std::cout << "Allocated string at: " << strPtr << ", value: " << *strPtr << std::endl;
}
// 分配动态数组
void allocateDynamicArray() {
size_t arraySize = 10;
size_t* const arrayPtr = new size_t[arraySize];
std::cout << "Allocated array of " << arraySize << " ints at: " << arrayPtr << std::endl;
for (size_t i = 0; i < arraySize; ++i) {
arrayPtr[i] = i;
}
}
// 使用mmap分配内存
void allocateMmap() {
size_t mmapSize = 4096; // 4KB
const void* mmapPtr = mmap(nullptr, mmapSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mmapPtr == MAP_FAILED) {
perror("mmap failed");
} else {
std::cout << "Allocated mmap at: " << mmapPtr << ", size: " << mmapSize << " bytes" << std::endl;
}
}
void allocateMemory() {
allocateIntArray();
allocateDouble();
allocateString();
allocateDynamicArray();
allocateMmap();
}
int main() {
usleep(100000); // 100ms
allocateMemory();
while (true) {
usleep(100000); // 100ms
}
return 0;
}
5. 注意事项
-
编译
libjemalloc.a
时请记得添加CFLAGS="-fPIC"
:sh./configure --prefix=/usr/local --enable-prof CFLAGS="-fPIC"
-
liballoc_hook.so
必须是静态链接libjemalloc.a
。 -
liballoc_hook.so
需要动态链接libpthread.so
,编译时记得切回动态链接方式:shgcc -o liballoc_hook.so -shared -fPIC alloc_hook.c -Wl,-Bstatic -ljemalloc -Wl,-Bdynamic -lpthread
-
请勿使用动态加载
libjemalloc.so
。如果使用如下命令:shLD_PRELOAD="/path/to/liballoc_hook.so /usr/local/lib/libjemalloc.so" ./example
jeprof
解析heap的结果会显示为:
sh
$ jeprof --show_bytes --text --lines ./example ./jeprof.out.60571.0.m0.heap
Using local file ./example.
Using local file ./jeprof.out.60571.0.m0.heap.
Total: 83512 B
83512 100.0% 100.0% 83512 100.0% prof_backtrace_impl /tmp/jemalloc-5.3.0/src/prof_sys.c:103
0 0.0% 100.0% 568 0.7% 0x00005610af62de49 ??:0
0 0.0% 100.0% 448 0.5% 0x00005610af62df3b ??:0
0 0.0% 100.0% 8 0.0% 0x00005610af62df8e ??:0
0 0.0% 100.0% 32 0.0% 0x00005610af62e039 ??:0
0 0.0% 100.0% 80 0.1% 0x00005610af62e137 ??:0
0 0.0% 100.0% 448 0.5% 0x00005610af62e299 ??:0
0 0.0% 100.0% 8 0.0% 0x00005610af62e29e ??:0
0 0.0% 100.0% 32 0.0% 0x00005610af62e2a3 ??:0
0 0.0% 100.0% 80 0.1% 0x00005610af62e2a8 ??:0
0 0.0% 100.0% 568 0.7% 0x00005610af62e2c3 ??:0
可以看到example.cpp
部分的信息无法显示,因此不可使用LD_PRELOAD
同时加载liballoc_hook.so
和libjemalloc.so
。
6. jemalloc的限制
尽管jemalloc
在内存管理和性能分析方面具有强大的功能,但它也存在一些限制:
-
无法hook mmap
jemalloc
无法hook通过mmap
或munmap
进行的内存分配。这意味着如果程序中大量使用mmap
进行内存分配,这部分内存不会被jemalloc
监控和管理,也不会包含在jemalloc
的内存分析报告中。因此,对于需要分析这种内存分配行为的程序,jemalloc
可能不是最佳选择。 -
无法hook线程相关信息
jemalloc
无法直接监控线程的创建和销毁。这对于某些需要详细分析线程行为的应用程序来说是一个限制。尽管jemalloc
可以通过配置和编译选项优化内存分配以适应多线程环境,但它不能提供与线程操作相关的详细信息。 -
无法hook直接系统调用的内存分配
如果程序通过直接系统调用(如
brk
或其他系统级内存分配调用)分配内存,这些调用将绕过jemalloc
的内存管理机制。因此,jemalloc
无法跟踪这些内存分配行为,导致分析结果不完整。 -
高采样率对性能的影响
开启高采样率(如
lg_prof_sample:0
)会显著影响程序的性能。虽然高采样率能够提供更详细和频繁的内存分配数据,但它也会导致程序运行速度变慢。因此,在生产环境中需要权衡采样率和性能之间的关系。 -
配置和使用复杂度
正确配置和使用
jemalloc
需要一定的专业知识和经验。对于不熟悉内存管理和性能分析的开发者来说,jemalloc
的配置选项和参数可能显得复杂,容易出错。因此,在使用jemalloc
进行内存分析之前,建议详细阅读官方文档并进行充分测试。 -
与其他内存管理库的兼容性问题
在某些情况下,
jemalloc
可能与其他内存管理库或工具产生兼容性问题。这可能导致程序在链接和运行时遇到问题。因此,在将jemalloc
集成到现有项目时,需要进行全面的测试以确保兼容性。
总的来说,尽管jemalloc
是一款功能强大的内存管理库,但在使用过程中需要注意其自身的限制,并根据具体需求进行权衡和取舍。