内存泄漏治理实战:TDengine 研发团队使用 Windbg 的经验分享

简介: 内存泄漏是一种常见的问题,它会导致程序的内存占用逐渐增加,最终导致系统资源耗尽或程序崩溃。这次内存泄漏问题发生在 Windows 下,TDengine 研发选择使用 Windbg 来解决问题。结果证明,在 Windows 下,使用 Windbg 也是一个不错的选择。

内存泄漏是一种常见的问题,它会导致程序的内存占用逐渐增加,最终导致系统资源耗尽或程序崩溃。AddressSanitizer (ASan) 和 Valgrind 是很好的内存检测工具,TDengine 的 CI 过程就使用了 ASan 。不过这次内存泄漏问题发生在 Windows 下,我们 CI 暂时还没有覆盖到,因此 TDengine 研发选择使用 Windbg 来解决问题。结果证明,在 Windows 下,使用 Windbg 也是一个不错的选择。

内存泄漏的常用检测方法

内存泄漏通常会发生在以下情况下:

  • 程序未正确释放已分配的内存
  • 程序中存在循环引用,导致垃圾收集器无法回收内存
  • 程序中存在内存泄漏的第三方库或组件

内存泄漏的检测方法主要包括以下几种:

  1. 静态代码分析工具:未释放的指针或内存分配错误等问题,不能检测在程序运行时动态分配内存的情况。
  2. 动态分析工具:可以使用内存分配和释放跟踪器来跟踪程序中的内存分配和释放操作,并检测是否存在内存泄漏的情况。然而,使用某些工具(如Valgrind)可能会对程序的性能产生一定的影响。
  3. 调试器:WinDbg 和 GDB。

优缺点:

  • 静态代码分析工具可以在早期发现问题,但是它们不能检测程序运行时动态分配内存的情况。
  • 动态分析工具可以在程序运行时检测问题,但是它们可能会影响程序性能,并且在检测大型应用程序时可能需要大量的时间和资源。不过在资源充足的测试环境中跑的话,就都不是问题了,比如 ASan 就帮我们发现过不少问题。
  • 调试器可以在程序运行时检测问题,并提供强大的分析工具。

实践分析

基本原理

使用 Windbg 定位内存泄露,依赖 glags 组件记录程序在运行期间所有申请和释放的内存,同时记录的还有申请内存时的调用栈信息。这样在程序运行期间,使用 umdh 组件进行两次快照记录,通过比较两次快照信息的差异,就可以发现两次快照间隔时间段中申请却并未释放的内存申请信息。如果有内存泄露,diff 结果最前边一般就是泄漏点的调用栈信息。当然,两次快照期间,要尽量触发内存泄露,才能更准确的定位。diff 结果中还会有少量正常的申请没来得及释放的调用信息,不过 diff 结果中能看到调用次数,比较容易甄别。

问题介绍

taosdump 在 windows 导入数据出错:

build and install latest TDengine 3.0 branch on Windows
use "taosBenchmark -I stmt -y" to create a lot of tables and data (10000 * 10000).
use "taosdump -D test -o outputFile" to dump out
use "taos -s 'drop database test'" to drop database
use "taosdump -i inputFile" to dump in.

错误日志:taosd “tsem_init failed, errno: 28”

Taosdump: dumpInAvroDataImpl() LN7039 taos_stmt_execute() failed! reason: Out of Memory, timestamp: 1500000009256

定位过程

配置 gflags

gflags 工具应该位于路径:C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\gflags,如果没有的话,可以直接前往 Microsoft 的官方网站下载安装:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/debugger/debugger-download-tools

安装完成后,在命令行执行 gflags.exe /i your_application.exe 可设置跟踪目标,同时可以设置相关参数。双击运行也是可以的,Image File 对应 /i 参数,选择启动程序 your_application.exe 后先按 tab 键,然后选择其他配置。

定位步骤
  1. 启动 your_application.exe(我要调试的是 taosdump.exe,所以下边是 taosdump.exe)

“C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\gflags” -i taosdump.exe +ust

  1. 拷贝 pdb 文件到 mysymbols 目录,pdb 文件存储了编译后的程序的调试信息,和可执行程序一起生成,可以在应用程序生成目录中找到。
  2. Set pdb 目录
set _NT_SYMBOL_PATH=c:\mysymbols;srv*c:\mycache*https://msdl.microsoft.com/download/symbols
  1. 生成第一次内存快照
"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\umdh" -pn:taosdump.exe -f:C:\xstest\umdhlog\taosdump11.log
  1. 生成第二次内存快照
"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\umdh" -pn:taosdump.exe -f:C:\xstest\umdhlog\taosdump12.log
  1. 生成快照比较结果(umdh)
"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\umdh"  C:\xstest\umdhlog\taosdump11.log C:\xstest\umdhlog\taosdump12.log -f:C:\xstest\umdhlog\taosdumpdiff11_12.log

分析与解决

结果文件

因为 taosdump 程序启动后直至退出都在做大量的业务工作,内存泄露很容易发生在两次快照期间。 988040 – 6ecf0 表示”申请次数 – 释放次数”, 很明显发生了内存泄露,泄漏点在 buildRequest 函数的 sem_init 这里。

+  919350 ( 988040 - 6ecf0)  201b0 allocs        BackTrace9CB6973F
+   1ea5c ( 201b0 -  1754)        BackTrace9CB6973F        allocations
        ntdll!RtlpAllocateHeapInternal+948D5
        taos!heap_alloc_dbg_internal+1F6 (minkernel\crts\ucrt\src\appcrt\heap\debug_heap.cpp, 359)
        taos!heap_alloc_dbg+4D (minkernel\crts\ucrt\src\appcrt\heap\debug_heap.cpp, 450)
        taos!_calloc_dbg+6C (minkernel\crts\ucrt\src\appcrt\heap\debug_heap.cpp, 518)
        taos!calloc+2E (minkernel\crts\ucrt\src\appcrt\heap\calloc.cpp, 30)
        taos!sem_init+5D (C:\workroom\TDengine\contrib\pthread\sem_init.c, 109)
        taos!buildRequest+209 (C:\workroom\TDengine\source\client\src\clientImpl.c, 192)
        taos!stmtCreateRequest+73 (C:\workroom\TDengine\source\client\src\clientStmt.c, 15)
        taos!stmtSetTbName+115 (C:\workroom\TDengine\source\client\src\clientStmt.c, 588)
        taos!taos_stmt_set_tbname+7F (C:\workroom\TDengine\source\client\src\clientMain.c, 1350)
        taosdump!dumpInAvroDataImpl+E25 (C:\workroom\TDengine\tools\taos-tools\src\taosdump.c, 6260)
        taosdump!dumpInOneAvroFile+3D2 (C:\workroom\TDengine\tools\taos-tools\src\taosdump.c, 7229)
        taosdump!dumpInAvroWorkThreadFp+20B (C:\workroom\TDengine\tools\taos-tools\src\taosdump.c, 7306)
        taosdump!ptw32_threadStart+CD (C:\workroom\TDengine\contrib\pthread\ptw32_threadStart.c, 233)
        taosdump!thread_start<unsigned int (__cdecl*)(void *),1>+9C (minkernel\crts\ucrt\src\appcrt\startup\thread.cpp, 97)
        KERNEL32!BaseThreadInitThunk+10
        ntdll!RtlUserThreadStart+2B
泄漏点修改

接下来查看代码并修改,C 语言对内存的使用自由度很高,因此也比较麻烦。可以看到有些路径遗漏了 tsem_destory 的调用。

更加详细的代码方案请见 https://github.com/taosdata/TDengine/pull/19580

总结

工欲善其事必先利其器,掌握更多的工具和手段,在解决问题时才能比较从容,Windbg 定位内存泄漏的方式非常简单,但是很有效。不过需要注意,它依赖 pdb 文件,因此,发布应用程序时要记得保留 pdb 文件。pdb 文件包含了程序的符号信息,能够帮助我们在调试过程中准确定位问题所在。

另外,从出问题的代码可以看出,这块内存的管理方式还是比较容易出错,RAII 机制能较好的避免资源泄露,C 语言中也可以通过模拟 RAII 来达到类似的效果,虽然没有 C++ 那么流畅,也许以后可以考虑优化一下。

RAII(Resource Acquisition Is Initialization)机制是一种重要的资源管理方式,它将资源的获取和对象的生命周期关联起来。通过在对象的构造函数中获取资源,在析构函数中释放资源,我们可以确保资源的正确管理,防止资源泄漏和内存泄漏等问题。RAII 机制在 C++ 等编程语言中得到广泛应用,是一种有效的资源管理方式。

目录
相关文章
|
7月前
|
编译器 程序员 C语言
C语言从入门到实战——动态内存管理
在C语言中,动态内存管理是指程序运行时,通过调用特定的函数动态地分配和释放内存空间。动态内存管理允许程序在运行时根据实际需要来分配内存,避免了静态内存分配在编译时就确定固定大小的限制。
75 0
|
2月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
55 2
|
4月前
|
NoSQL Java 测试技术
Golang内存分析工具gctrace和pprof实战
文章详细介绍了Golang的两个内存分析工具gctrace和pprof的使用方法,通过实例分析展示了如何通过gctrace跟踪GC的不同阶段耗时与内存量对比,以及如何使用pprof进行内存分析和调优。
94 0
Golang内存分析工具gctrace和pprof实战
|
7月前
|
算法 Java Python
【Python 的内存管理机制专栏】Python 内存管理实战:性能优化与内存泄漏检测
【5月更文挑战第18天】Python内存管理关乎程序性能与稳定性。优化包括避免过多临时对象,如优化列表推导式减少对象创建。警惕循环引用造成的内存泄漏,如示例中的Node类。使用`gc`模块检测泄漏,通过`gc.set_debug(gc.DEBUG_LEAK)`和`gc.collect()`获取信息。实践中需持续分析内存使用,优化算法、数据结构和资源释放,以提升程序质量与效率。
75 9
【Python 的内存管理机制专栏】Python 内存管理实战:性能优化与内存泄漏检测
|
6月前
|
存储 安全 Java
SpringSecurity6从入门到实战之初始用户如何存储到内存
Spring Security 在 SpringBoot 应用中默认使用 `UserDetailsServiceAutoConfiguration` 类将用户信息存储到内存中。当classpath有`AuthenticationManager`、存在`ObjectPostProcessor`实例且无特定安全bean时,此配置生效。`inMemoryUserDetailsManager()`方法创建内存用户,通过`UserDetails`对象填充`InMemoryUserDetailsManager`的内部map。若要持久化到数据库,需自定义`UserDetailsService`接口实
|
5月前
|
算法 Java 开发者
Java面试题:Java内存探秘与多线程并发实战,Java内存模型及分区:理解Java堆、栈、方法区等内存区域的作用,垃圾收集机制:掌握常见的垃圾收集算法及其优缺点
Java面试题:Java内存探秘与多线程并发实战,Java内存模型及分区:理解Java堆、栈、方法区等内存区域的作用,垃圾收集机制:掌握常见的垃圾收集算法及其优缺点
39 0
|
5月前
|
安全 Java 调度
Java面试题:Java内存优化、多线程安全与并发框架实战,如何在Java应用中实现内存优化?在多线程环境下,如何保证数据的线程安全?使用Java并发工具包中的哪些工具可以帮助解决并发问题?
Java面试题:Java内存优化、多线程安全与并发框架实战,如何在Java应用中实现内存优化?在多线程环境下,如何保证数据的线程安全?使用Java并发工具包中的哪些工具可以帮助解决并发问题?
64 0
|
7月前
|
监控 Java 测试技术
JVM工作原理与实战(二十八):内存溢出和内存泄漏
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了内存溢出与内存泄漏、内存泄漏的常见场景、解决内存溢出的步骤等内容。
165 0
JVM工作原理与实战(二十八):内存溢出和内存泄漏
|
7月前
|
缓存 移动开发 Android开发
构建高效Android应用:内存优化实战指南
【4月更文挑战第25天】 在移动开发领域,应用程序的性能至关重要。特别是对于Android设备,由于硬件配置的多样性,合理的内存管理与优化是提升应用流畅度、减少卡顿和崩溃的关键。本文将深入探讨Android应用的内存优化技巧,通过分析内存泄漏的原因、诊断工具的运用以及实际代码层面的改进措施,帮助开发者构建更加高效的Android应用。
|
7月前
|
存储 并行计算 Java
NumPy内存管理与性能调优实战
【4月更文挑战第17天】本文探讨了NumPy的内存管理和性能调优,强调了连续内存分配、选择合适的内存分配函数及及时释放内存的重要性。优化策略包括使用内置函数、向量化操作、避免数据复制和利用并行计算。实战案例展示了如何通过向量化操作替换Python循环提升计算效率。通过理解和应用这些技巧,开发者可提高NumPy程序的性能。