log4cxx库内存泄露解决办法

来源:互联网 发布:php大型项目框架 编辑:程序博客网 时间:2024/06/01 07:44

背景

公司的系统使用log4cxx作为日志库,近期将程序迁移到Linux环境,结果发现非常严重的内存泄露。经过分析,将内存定位到log4cxx。使用的版本为0.9.7

分析

分析log4cxx库发现,其使用引用计数控制动态内存的释放,所以在打日志的时候,会有以下的核心代码:

void Logger::forcedLog(const LevelPtr& level, const String& message,    const char* file, int line){    callAppenders(new LoggingEvent(FQCN, this, level, message, file, line));}void Logger::forcedLog(const String& fqcn, const LevelPtr& level, const String& message,            const char* file, int line){    callAppenders(new LoggingEvent(fqcn, this, level, message, file, line));}

上述代码中,new出来的对象指针存放在一个指针对象中,在callAppenders调用结束之后,该指针对象将会析构。在其析构过程中,会通过引用计数去释放new出来的LoggingEvent对象,从而达到内存释放的目的。

现在问题出来了,new出来的对象实际上并未被释放,log4cxx的引用计数机制在linux下并没有生效。引用计数核心代码在objectimpl.cpp中,如下

void ObjectImpl::addRef() const{    Thread::InterlockedIncrement(&ref);}void ObjectImpl::releaseRef() const{    if (Thread::InterlockedDecrement(&ref) == 0)    {           delete this;    }   }

我们在看看InterlockedIncrement 和 InterlockedDecrement的具体实现源码,在thread.cpp中,如下

long Thread::InterlockedIncrement(volatile long * val){   #ifdef __GLIBCPP__    return __exchange_and_add((volatile _Atomic_word *)val, 1 ) + 1;#elif defined(__i386__)    long ret;        __asm__ __volatile__ ("lock; xaddl %0, %1"                  : "=r" (ret), "=m" (*val)                  : "0" (1), "m" (*val));    return ret+1;#elif defined(sparc) && defined(__SUNPRO_CC)    sparc_atomic_add_32(val, 1);    return *val;#elif defined(HAVE_MS_THREAD)#if _MSC_VER == 1200    // MSDEV 6    return ::InterlockedIncrement((long *)val);#else    return ::InterlockedIncrement(val);#endif // _MSC_VER    return *val + 1 // unsafe#endif}long Thread::InterlockedDecrement(volatile long * val){#ifdef __GLIBCPP__    return __exchange_and_add((volatile _Atomic_word *)val, -1 ) - 1;#elif defined(__i386__)    long ret;    __asm__ __volatile__ ("lock; xaddl %0, %1"                  : "=r" (ret), "=m" (*val)                  : "0" (-1), "m" (*val));    return ret-1;#elif defined(sparc) && defined(__SUNPRO_CC)    sparc_atomic_add_32(val, -1);    return *val;#elif defined(HAVE_MS_THREAD)#if _MSC_VER == 1200    // MSDEV 6    return ::InterlockedDecrement((long *)val);#else    return ::InterlockedDecrement(val);#endif // _MSC_VER    return *val - 1; // unsafe#endif}

这段代码非常让人纠结,根本看不懂啊,这么多环境相关的宏,谁知道走的是哪段代码啊?没办法,只好写一个程序测试自己的Linux系统都命中了哪些宏,测试的结果让人大跌眼镜。接口中的宏没有一个命中!!坑爹啊,这是。结果在Linux下,这2个接口都成为了空函数,啥也没干,连return语句都没有。注意,接口中//unsafe那一行其实也没有编译到代码中!

我以为//unsafe对应的代码段应该是所有宏都未定义的时候所编译的代码段,于是加上#else让其能够得到编译,结果。。。程序运行出core;好吧,是因为没有完成实际的计数值增减操作,那就加上代码完成增减,为此我使用了各种能想到的linux原子级加锁增减接口,结果还是不行,仍然出core。最后都快崩溃了。

解决

经过各种尝试不行之后,突然想到,是否可以抛弃log4cxx中的引用计数机制呢?于是回到前面的Logger::forcedLog接口。不就是内存释放,不用引用计数,手动释放还不行吗?于是我修改了这段代码,如下

void Logger::forcedLog(const LevelPtr& level, const String& message,    const char* file, int line){    LoggingEvent* pLog = new LoggingEvent(FQCN, this, level, message, file, line);    callAppenders(pLog);    delete pLog;}void Logger::forcedLog(const String& fqcn, const LevelPtr& level, const String& message,            const char* file, int line){    LoggingEvent* pLog = new LoggingEvent(fqcn, this, level, message, file, line);    callAppenders(pLog);    delete pLog;}

这样,每一次打日志之后都手动释放内存。然后就是编译、测试,多线程运行,结果内存非常稳定,done!

总结

开源库不是万能的,条件编译也不是万能的,总会出现这样那样不同的环境导致库出现一些问题。好在可以看到源码。所以还是有办法解决的。当然,作者的解决方案其实并不是特别完美的,也有可能会出现其他的问题,希望各位大神不吝指教~


更多技术博客,请关注:www.chenkeblog.com