log4cxx库内存泄露解决办法
背景公司的系统使用log4cxx作为日志库,近期将程序迁移到Linux环境,结果发现非常严重的内存泄露。经过分析,将内存定位到log4cxx。使用的版本为0.9.7分析分析log4cxx库发现,其使用引用计数控制动态内存的释放,所以在打日志的时候,会有以下的核心代码:void Logger::forcedLog(const LevelPtr& level, const String
背景
公司的系统使用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
更多推荐
所有评论(0)