PHP内存泄漏代码的常用代码案例有哪些

您当前所在位置: >
> 编辑语言
轻轻松松搞定C++内存泄漏
C++内存泄漏大家会处理吗?C++内存分配与释放均由用户代码自行控制,灵活的机制有如潘多拉之盒,即让程序员有了更广的发挥空间,也产生了代代相传的内存泄漏问题。对于新手来说,最常犯的错误就是new出一个对象而忘记释放,对于一般小应用程序来说,一点内存空间不算什么。但是当内存泄漏问题出现在需要24小时运行的平台类程序上的时候,将会使系统可用内存飞速减少,最后耗尽系统资源,导致系统崩溃。
所以学会如何防止并检查内存泄漏,是一个合格的c++程序员必须具备的能力。但是由于内存泄漏是程序运行并满足一定条件时才会发生,直接从代码中查出泄漏原因的难度较大,而且一旦内存泄漏发生在多线程程序中,从大量的代码中要靠人工找出泄漏原因,无论对新人还是老手都是一场噩梦。
本文介绍一种在vs2003中检查内存泄漏的方法,供各位新人老手参考,在vc6中实现需要做一些变动,详情可自行参照相关资料。
检查策略分析
首先,假定我们需要检测一个24小时运行的平台程序的内存泄漏情况,我们无法确定具体的内存泄漏速度,但是我们可以确定该程序在一定时间内(如10分钟)泄漏的内存量是接近的,设为L(eak)。
考虑在10分钟的运行时间内程序新申请到的内存A(lloc),这部分内存其实包含了程序运行正常申请,并会在后续运行中进行释放的普通内存块N(ormal)和泄漏的内存L,即:
在后续的运行中,由于N部分不断的申请和释放,所以这部分的总量基本上是不变的,而L部分由于只申请而不释放,占用的内存总量将会越来越大。
将这个结果放到运行时间轴上,现在我们观察程序运行中的20分钟,我们假定内存泄漏速度为dL/10分钟,时间轴如下:
-----------------------------------------------------------------------------------
Tn-2 Tn-1 Tn
三点间隔均为10分钟,则我们有如下结论:
Tn点总的内存分配量 An = N + dL * n,N为正常分配内存,dL*n为内存泄漏量的总和,而Tn-1点的内存总量则为 An-1 = N + dL*(n-1)。注意,我们这里不考虑释放的内存量,仅考虑增加的内存量。因此很明显单位时间内的内存泄漏量 dL = An - An-1。
生成内存Dump文件的代码实现
要完成如上的策略,我们首先需要能跟踪内存块的分配与释放情况,并且在运行时将分配情况保存到文件中,以便进行比较分析,所幸m$已经为我们提供了一整套手段,可以方便地进行内存追踪。具体实现步骤如下:
包含内存追踪所需库
在StdAfx.h中添加如下代码,注意必须定义宏_CRTDBG_MAP_ALLOC,否则后续dump文件将缺少内存块的代码位置。
#ifdef _DEBUG
//for memory leak check
#define _CRTDBG_MAP_ALLOC //使生成的内存dump包含内存块分配的具体代码为止
启动内存追踪
上述步骤完成后,则可以在应用程序启动处添加如下代码,启动内存追踪,启动后程序将自动检测内存的分配与释放情况,并允许将结果输出。
//enable leak check
_CrtSetDbgFlag( _CRTDBG_REPORT_FLAG);
将结果输出指向dump文件
由于默认情况下,内存泄漏的dump内容是输出到vs的debug输出窗口,但是对于服务类程序肯定没法开着vs的debug模式来追踪内存泄漏,所以必须将dump内容的输出转到dump文件中。在程序中添加如下部分:
HANDLE hLogF//声明日志文件句柄
hLogFile = CreateFile(&./log/memleak.log&, GENERIC_WRITE, FILE_SHARE_WRITEFILE_SHARE_READ,
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);//创建日志文件
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);//将warn级别的内容都输出到文件(注意dump的报告级别即为warning)
_CrtSetReportFile(_CRT_WARN, hLogFile);//将日志文件设置为告警的输出文件
保存内存Dump
完成了以上的设置,我们就可以在程序中添加如下代码,输出内存dump到指定的dump文件中:
_CrtMemState s1, s2, s3;//定义3个临时内存状态
_CrtDumpMemoryLeaks();//Dump从程序开始运行到该时刻点,已分配而未释放的内存,即前述An
//以下部分非必要,仅为方便后续分析增加信息
_CrtMemCheckpoint( &s2 );
if ( _CrtMemDifference( &s3, &s1, &s2) )
_CrtMemDumpStatistics( &s3 );//dump相邻时间点间的内存块变化
//for next compare
_CrtMemCheckpoint( &s1 );
time_t now = time(0);
strUCt tm *nowTime = localtime(&now);
_RPT4(_CRT_WARN,&%02d %02d:%02d:%02d snapshot dump.\n&,
nowTime-&tm_mday, nowTime-&tm_hour,nowTime-&tm_min,nowTime-&tm_sec);//输出该次dump时间
以上代码最好放在一个函数中由定时器定期触发,或者手动snapshot生成相等时间段的内存dump。
dump文件内容示例如下:
Detected memory leaks!
Dumping objects -&
{} normal block at 0x05C4C490, 87 bytes long.
Data: & & 02 00 1D 90 84 9F A6 89 00 00 00 00 00 00 00 00
d:\xxxxx\xxxworker.cpp(903) : {} normal block at 0x05D3EF90, 256 bytes long.
Data: & & 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Object dump complete.
0 bytes in 0 Free Blocks.
215968 bytes in 876 Normal Blocks.
0 bytes in 0 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 220044 bytes.
Total allocations: 7838322 bytes.
10 16:29:14 snapshot dump.
上面红色部分即为用户代码中分配而未释放的内存块位置。
解析Dump文件
前面我们已经通过dump文件获取到各时刻点的内存dump,根据前面的分析策略,我们只需要将第n次dump的内存块分配情况An,与第n-1次dump内存块分配情况An-1作比较,即可定位到发生内存泄漏的位置。由于dump文件一般容量巨大,靠人工进行对比几乎不可能,所以仅介绍比较的思路,各位需要自行制作小工具进行处理。
1、提取两个相邻时间点的dump文件D1和D2,设D1是D2之前的dump
2、各自提取dump文件中用户代码分配的内存块(即有明确代码位置,而且为normal block的内存块),分别根据内存块ID(如&d:\xxxxx\xxxworker.cpp(903) : {}&红色部分)保存在列表L1和L2
3、遍历列表L2,记录内存块ID没有在L1中出现过的内存块,这些内存块即为可能泄漏的内存
4、根据3的结果,按照内存的分配代码位置,统计各处代码泄漏的内存块个数,降序排列,分配次数越多的代码,内存泄漏可能性越大。
以上就是我们给大家介绍的C++内存泄漏了。希望大家继续关注我们的网站!
上一篇:下一篇:
本文相关阅读
高校查询分数线
考生所在地
北京天津辽宁吉林黑龙江上海江苏浙江安徽福建山东湖北湖南广东重庆四川陕西甘肃河北山西内蒙古河南海南广西贵州云南西藏青海宁夏新疆江西香港澳门台湾
<option value="
<option value="
<option value="
<option value="
<option value="
<option value="
<option value="
<option value="
<option value="
<option value="
<option value="
地区批次线查询
考生所在地
北京天津辽宁吉林黑龙江上海江苏浙江安徽福建山东湖北湖南广东重庆四川陕西甘肃河北山西内蒙古河南海南广西贵州云南西藏青海宁夏新疆江西香港澳门台湾
科目理科文科综合其他
<option value="
<option value="
<option value="
<option value="
<option value="
<option value="
<option value="
<option value="
<option value="
<option value="
<option value="
院校所在地北京天津辽宁吉林黑龙江上海江苏浙江安徽福建山东湖北湖南广东重庆四川陕西甘肃河北山西内蒙古河南海南广西贵州云南西藏青海宁夏新疆江西香港澳门台湾
其它中央部委
类型工科农业师范民族
层次本科高职(专科)
高考志愿③部曲
频道热门推荐
栏目最新更新[iOS 源代码分享] MLeaksFinder 可帮助您在开发时查找您的 iOS 应用程序中的内存泄漏 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
已注册用户请 &
Sponsored by
国内领先的实时通信云为开发者和企业提供安全可靠的场景化实时通信云服务
Promoted by
[iOS 源代码分享] MLeaksFinder 可帮助您在开发时查找您的 iOS 应用程序中的内存泄漏
· 9 天前 · 276 次点击
项目介绍地址:
MLeaksFinder helps you find memory leaks in your iOS apps at develop time. It can automatically find leaks in UIView and UIViewController objects, present an alert with the leaked object in its View-ViewController stack when leaks happening. More over, it can try to find a retain cycle for the leaked object using FBRetainCycleDetector. Besides finding leaks in UIView and UIViewController objects, developers can extend it to find leaks in other kinds of objects.
MLeaksFinder 可帮助您在开发时查找您的 iOS 应用程序中的内存泄漏。 它可以自动查找 UIView 和 UIViewController 对象中的泄漏,当泄漏发生时,在其 View-ViewController 堆栈中显示泄漏对象的警报。 更多,它可以尝试使用 FBRetainCycleDetector 找到泄漏对象的保留周期。 除了在 UIView 和 UIViewController 对象中查找泄漏,开发人员可以扩展它以查找其他类型对象中的泄漏。
Communication
Installation
pod 'MLeaksFinder'
MLeaksFinder comes into effect after pod install, there is no need to add any code nor to import any header file.
MLeaksFinder 在`pod install'之后生效,不需要添加任何代码或导入任何头文件。
MLeaksFinder can automatically find leaks in UIView and UIViewController objects. When leaks happening, it will present an alert with the leaked object in its View-ViewController stack.
MLeaksFinder 可以自动查找 UIView 和 UIViewController 对象中的泄漏。 当泄漏发生时,它将在其 View-ViewController 堆栈中提供泄漏对象的警报。
Memory Leak
MyTableViewController,
UITableView,
UITableViewWrapperView,
MyTableViewCell
For the above example, we are sure that objects of MyTableViewController, UITableView, UITableViewWrapperView are deallocated successfully, but not the objects of MyTableViewCell.
MLeaksFinder can also try to find a retain cycle for the leaked object using FBRetainCycleDetector.
对于上面的例子,我们确信“ MyTableViewController ”,“ UITableView ”,“ UITableViewWrapperView ”的对象被成功解除分配,但不是“ MyTableViewCell ”的对象。
MLeaksFinder 也可以尝试使用 FBRetainCycleDetector 查找泄漏对象的保留循环。
Retain Cycle
"-& MyTableViewCell ",
"-& _callback -& __NSMallocBlock__ "
With the output, we know that the object of MyTableViewCell has a __strong instance variable named _callback, which is of type __NSMallocBlock__. And _callback also has a __strong reference back to MyTableViewCell.
使用输出,我们知道MyTableViewCell的对象有一个名为_callback的__strong实例变量,它的类型为__NSMallocBlock__。 和_callback也有一个__strong引用返回MyTableViewCell。
Mute Assertion
If your class is designed as singleton or for some reason objects of your class should not be dealloced, override - (BOOL)willDealloc in your class by returning NO.
如果你的类被设计为单例,或者由于某些原因你的类的对象不应该被释放,在你的类中重写- ( BOOL ) willDealloc,返回 NO 。
- (BOOL)willDealloc {
return NO;
Find Leaks in Other Objects
MLeaksFinder finds leaks in UIView and UIViewController objects by default. However, you can extend it to find leaks in the whole object graph rooted at a UIViewController object.
默认情况下, MLeaksFinder 在 UIView 和 UIViewController 对象中找到泄漏。 但是,您可以扩展它以在基于 UIViewController 对象的整个对象图中查找泄漏。
- (BOOL)willDealloc {
if (![super willDealloc]) {
return NO;
MLCheck(self.viewModel);
return YES;
1 回复 &| &直到
17:34:58 +08:00
这个确实很好用,可以自动检测 vc 和 view 的循环引用,推荐!
& · & 1263 人在线 & 最高记录 2399 & · &
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.7.5 · 43ms · UTC 13:22 · PVG 21:22 · LAX 05:22 · JFK 08:22? Do have faith in what you're doing.ThreadLocal 内存泄露的实例分析 - 文章 - 伯乐在线
& ThreadLocal 内存泄露的实例分析
之前写了一篇是从理论上分析ThreadLocal的内存泄漏问题,这一篇文章我们来分析一下实际的内存泄漏案例。分析问题的过程比结果更重要,理论结合实际才能彻底分析出内存泄漏的原因。
案例与分析
在 Tomcat 中,下面的代码都在 webapp 内,会导致WebappClassLoader泄漏,无法被回收。
public class MyCounter {
private int count = 0;
public void increment() {
public int getCount() {
public class MyThreadLocal extends ThreadLocal {
public class LeakingServlet extends HttpServlet {
private static MyThreadLocal myThreadLocal = new MyThreadLocal();
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
MyCounter counter = myThreadLocal.get();
if (counter == null) {
counter = new MyCounter();
myThreadLocal.set(counter);
response.getWriter().println(
"The current thread served this servlet " + counter.getCount()
+ " times");
counter.increment();
123456789101112131415161718192021222324252627282930313233
public class MyCounter {&&&&&&&&private int count = 0;&&&&&&&&&public void increment() {&&&&&&&&&&&&&&&&count++;&&&&&&&&}&&&&&&&&&public int getCount() {&&&&&&&&&&&&&&&&return count;&&&&&&&&}}&public class MyThreadLocal extends ThreadLocal {}&public class LeakingServlet extends HttpServlet {&&&&&&&&private static MyThreadLocal myThreadLocal = new MyThreadLocal();&&&&&&&&&protected void doGet(HttpServletRequest request,&&&&&&&&&&&&&&&&&&&&&&&&HttpServletResponse response) throws ServletException, IOException {&&&&&&&&&&&&&&&&&MyCounter counter = myThreadLocal.get();&&&&&&&&&&&&&&&&if (counter == null) {&&&&&&&&&&&&&&&&&&&&&&&&counter = new MyCounter();&&&&&&&&&&&&&&&&&&&&&&&&myThreadLocal.set(counter);&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&response.getWriter().println(&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"The current thread served this servlet " + counter.getCount()&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&+ " times");&&&&&&&&&&&&&&&&counter.increment();&&&&&&&&}}
上面的代码中,只要LeakingServlet被调用过一次,且执行它的线程没有停止,就会导致WebappClassLoader泄漏。每次你 reload 一下应用,就会多一份WebappClassLoader实例,最后导致 PermGen OutOfMemoryException。
现在我们来思考一下:为什么上面的ThreadLocal子类会导致内存泄漏?
WebappClassLoader
首先,我们要搞清楚WebappClassLoader是什么鬼?
对于运行在 Java EE容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。不同的 Web 容器的实现方式也会有所不同。以 Apache Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。
也就是说WebappClassLoader是 Tomcat 加载 webapp 的自定义类加载器,每个 webapp 的类加载器都是不一样的,这是为了隔离不同应用加载的类。
那么WebappClassLoader的特性跟内存泄漏有什么关系呢?目前还看不出来,但是它的一个很重要的特点值得我们注意:每个 webapp 都会自己的WebappClassLoader,这跟 Java 核心的类加载器不一样。
我们知道:导致WebappClassLoader泄漏必然是因为它被别的对象强引用了,那么我们可以尝试画出它们的引用关系图。等等!类加载器的作用到底是啥?为什么会被强引用?
类的生命周期与类加载器
要解决上面的问题,我们得去研究一下类的生命周期和类加载器的关系。这个问题说起来又是一篇文章,参考我做的笔记。
跟我们这个案例相关的主要是类的卸载:
在类使用完之后,如果满足下面的情况,类就会被卸载:
该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
加载该类的ClassLoader已经被回收。
该类对应的java.lang.Class对象没有任何地方被引用,没有在任何地方通过反射访问该类的方法。
如果以上三个条件全部满足,JVM 就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,Java 类的整个生命周期就结束了。
由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。Java虚拟机自带的类加载器包括根类加载器、扩展类加载器和系统类加载器。Java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象,因此这些Class对象始终是可触及的。
由用户自定义的类加载器加载的类是可以被卸载的。
注意上面这句话,WebappClassLoader如果泄漏了,意味着它加载的类都无法被卸载,这就解释了为什么上面的代码会导致 PermGen OutOfMemoryException。
关键点看下面这幅图
我们可以发现:类加载器对象跟它加载的 Class 对象是双向关联的。这意味着,Class 对象可能就是强引用WebappClassLoader,导致它泄漏的元凶。
引用关系图
理解类加载器与类的生命周期的关系之后,我们可以开始画引用关系图了。(图中的LeakingServlet.class与myThreadLocal引用画的不严谨,主要是想表达myThreadLocal是类变量的意思)
下面,我们根据上面的图来分析WebappClassLoader泄漏的原因。
LeakingServlet持有static的MyThreadLocal,导致myThreadLocal的生命周期跟LeakingServlet类的生命周期一样长。意味着myThreadLocal不会被回收,弱引用形同虚设,所以当前线程无法通过ThreadLocalMap的防护措施清除counter的强引用(见)。
强引用链:thread -& threadLocalMap -& counter -& MyCounter.class -& WebappClassLocader,导致WebappClassLoader泄漏。
内存泄漏是很难发现的问题,往往由于多方面原因造成。ThreadLocal由于它与线程绑定的生命周期成为了内存泄漏的常客,稍有不慎就酿成大祸。
本文只是对一个特定案例的分析,若能以此举一反三,那便是极好的。最后我留另一个类似的案例供读者分析。
假设我们有一个定义在 Tomcat Common Classpath 下的类(例如说在 tomcat/lib 目录下)
public class ThreadScopedHolder {
private final static ThreadLocal threadLocal = new ThreadLocal();
public static void saveInHolder(Object o) {
threadLocal.set(o);
public static Object getFromHolder() {
return threadLocal.get();
1234567891011
public class ThreadScopedHolder {&&&&&&&&private final static ThreadLocal threadLocal = new ThreadLocal();&&&&&&&&&public static void saveInHolder(Object o) {&&&&&&&&&&&&&&&&threadLocal.set(o);&&&&&&&&}&&&&&&&&&public static Object getFromHolder() {&&&&&&&&&&&&&&&&return threadLocal.get();&&&&&&&&}}
两个在 webapp 的类:
public class MyCounter {
private int count = 0;
public void increment() {
public int getCount() {
public class LeakingServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
MyCounter counter = (MyCounter)ThreadScopedHolder.getFromHolder();
if (counter == null) {
counter = new MyCounter();
ThreadScopedHolder.saveInHolder(counter);
response.getWriter().println(
"The current thread served this servlet " + counter.getCount()
+ " times");
counter.increment();
12345678910111213141516171819202122232425262728
public class MyCounter {&&&&&&&&private int count = 0;&&&&&&&&&public void increment() {&&&&&&&&&&&&&&&&count++;&&&&&&&&}&&&&&&&&&public int getCount() {&&&&&&&&&&&&&&&&return count;&&&&&&&&}}public class LeakingServlet extends HttpServlet {&&&&&&&&&protected void doGet(HttpServletRequest request,&&&&&&&&&&&&&&&&&&&&&&&&HttpServletResponse response) throws ServletException, IOException {&&&&&&&&&&&&&&&&&MyCounter counter = (MyCounter)ThreadScopedHolder.getFromHolder();&&&&&&&&&&&&&&&&if (counter == null) {&&&&&&&&&&&&&&&&&&&&&&&&counter = new MyCounter();&&&&&&&&&&&&&&&&&&&&&&&&ThreadScopedHolder.saveInHolder(counter);&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&response.getWriter().println(&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"The current thread served this servlet " + counter.getCount()&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&+ " times");&&&&&&&&&&&&&&&&counter.increment();&&&&&&&&}}
欢迎大家批评指正,留言交流。
打赏支持我写出更多好文章,谢谢!
打赏支持我写出更多好文章,谢谢!
任选一种支付方式
关于作者:
可能感兴趣的话题
请作者回答一下下面两个问题:
(1)什么样的场景或什么样的人,会设计一个永不停止的线程?
(不要提容器线程和后台自启动Job,这两种情况没有必要用ThreadLocal)
(2) 抛开threadlocal,来看下例子里的Servlet . 要知道servlet是单例多线程的,也就说容器会保留一个Servlet 实例,根据作者的强引用关系:
leakingServlet (容器保留的单例)-& LeakingServlet.class -& WebappClassLocader,导致WebappClassLoader泄漏。
那岂不是,只要用Servlet,就会导致 WebappClassLoader泄漏了. J2EE这么多年都是建立在内存泄露的基础上的?
关于伯乐在线博客
在这个信息爆炸的时代,人们已然被大量、快速并且简短的信息所包围。然而,我们相信:过多“快餐”式的阅读只会令人“虚胖”,缺乏实质的内涵。伯乐在线内容团队正试图以我们微薄的力量,把优秀的原创文章和译文分享给读者,为“快餐”添加一些“营养”元素。
新浪微博:
推荐微信号
(加好友请注明来意)
&#8211; 好的话题、有启发的回复、值得信赖的圈子
&#8211; 分享和发现有价值的内容与观点
&#8211; 为IT单身男女服务的征婚传播平台
&#8211; 优秀的工具资源导航
&#8211; 翻译传播优秀的外文文章
&#8211; 国内外的精选文章
&#8211; UI,网页,交互和用户体验
&#8211; 专注iOS技术分享
&#8211; 专注Android技术分享
&#8211; JavaScript, HTML5, CSS
&#8211; 专注Java技术分享
&#8211; 专注Python技术分享
& 2016 伯乐在线android内存分析
最近负责的Gallery模块测试时频繁报出使用一段时间后就发生Crash,通过Log查看发现是OOM引发,本篇文档记录解决该问题的分析过程。
该OOM问题测试没有找到复现步骤,只是报出使用一段时间就会发生OOM这个非常宽泛的现象,没有复现步骤,没有确定时间节点。查看Log每次发生OOM的代码调用栈也不尽相同。贴出出现OOM的几段Log。&
通过上面的几段Log,看出都是在申请一个较大的内存空间时,系统memory空间不够了,导致发生了OOM,这里都是在对图片进行操作,因此申请了较大的内存空间。&
至此有一个大概的查找方向:问题出现的时机是在Gallery创建大的Bitmap对象,了解过Gallery业务流程很容易就会怀疑这个过程多半发生在PhotoPage界面,也就是浏览大图界面,但深入了解发现Gallery已经对这块进行了处理,并不是大图有多大,就一次性创建多大的Bitmap对象,而是将一个大图分成多个区域加载进来,具体可以查看Gallery代码中LocalImage类中的requestLargeImage方法,它利用的是BitmapRegionDecoder技巧,分块多次加载大图,这里不赘述。在排查Log发现问题发生在refocus操作几率大,测试也印证了该点。这里refocus是MTK平台针对Gallery做的一个新功能,它能对景深图片进行重聚焦/调整图片光圈&#20540;。排查重点放在refocus操作上。
上述Log分析都只是逻辑上的分析,没有验证,针对这些分析需要工具帮忙确认进一步缩小排查范围。这里使用DDMS 工具分析,打开DDMS,选择Gallery所在的进程,点击Update Heap选项。切换到Heap视图边操作边查看内存变化.&
留意占用空间最多的项,这里是byte array,通过在AlbumSetPage/AlbumPage来回切换滑动观察到该项&#20540;大概稳定在30M~50M之前,并且data object/class object&#20540;也没有出现一直不断的增加的现象,点击Cause GC触发垃圾回收器工作,上述这些&#20540;也有明显的回落。&
在加入浏览大图的PhotoPage界面,来回在PhotoPage滚动浏览图片,观察到内存大概稳定在50~70M之间。&
当返回AlbumPage界面,点击Cause GC触发垃圾回收器工作,内存回落到之前的30M~50M区间。&
现在在加入refocus操作.发现进入refocus操作界面内存上升,退出后内存没有明显下降,曙光出现了。于是不停的进入退出refocus界面,大约来回二十多次后,问题复现了,logcat发现报错也是OOM,到此找到了复现问题的步骤。
MAT工具排查
有了上面的复现步骤,开始用MAT工具定性分析内存使用情况。&
利用命令:&
adb shell am dump heap [package name] &/data/local/tmp/gallery6.hprof&
生成hprof文件。&
再用命令:hprof-conv gallery6.hprof gallery6_.hprof转换成MAT工具可以识别的&#26684;式。&
最终生成的hprof文件如下:
点击Dominator Tree:&
可以看到这里有多个refocusView和RefocusActivity存在多个实例,这个很可能是突破口,&
先排查RefocusView单击鼠标右键,选择List Objects–&with incoming references
继续选中过滤出来的RefocusView,鼠标右键,选择&
Path to GC Roots–&exclude weak/soft references.
可以看到RefocusView—-&ApertureSlider—-&ValueAnimator的一条引用链。&
同样的方式在排查RefocusActivity发现跟RefocusView相关。
再看同样可疑的Bitmap对象还是指向RefocusView.
看到这,问题基本已经有结论了:&
RefocusActivity—&RefocusView—-&ApertureSlider—-&ValueAnimator这条引用关系链存在问题。&
为了快速验证分析结论是否正确,在代码中将ApertureSlider去除。&
再次检验结果如下:
可以看到没有再出现多个RefocusView/RefocusActivity/Bitmap实例了。&
复测也没有在出现OOM。
通过上面的一步步分析,我们已经找到了问题原因出在RefocusView上,那么它为什么没有GC回收呢,再次梳理下逻辑思路:
RefocusActivity---&RefocusActivity的onCreate里加载RefocusView&
----&RefocusView里持有ApertureSlider---&ApertureSlider持有ValueAnimator---&ValueAnimator没有被回收
现在进入ApertureSlider查看ValueAnimator,果然一进入就发现了ValueAnimator定义存在问题.&
这里声明了一个static 类型的ValueAnimator,并且在构造方法里对它设置了动画监听器。而正是这个静态类型对ApertureSlider保持了一个引用,按照上面梳理的思路,在反向推回去,最终导致了RefocusActivity无法被GC回收。找到了问题点,就好修改了,去掉 static final修饰符即可。从一个小小的变量引发内存泄漏,导致最后出现整个应用的Crash。开篇Log里提示的错误,算是正好撞在了枪口上,一次申请了较大的内存空间,将问题放大凸显了出来。
分析问题的思路
回过头来看,本篇的问题实在显而易见。但透过它还是能收获一些知识。
大胆假设:一个APP做到后期,往往代码量很大,成千上万的类文件,错综复杂的逻辑链条穿插其中。除非显而易见的问题,否则最好不要一头扎进细节处开始查询问题,先在大脑中过一遍发生问题的场景是什么?顺着脑海中的思路假设问题发生的条件。小心求证:有了猜测,就需要严谨的分析验证。在这个问题中,一开始单从Log中我猜测的是创建Bitmap对象太大了,代码中直接按大图进行decode,也确实在代码中找到了生成图片时设置的采样&#20540;是1(inSimpleSize=1),这样如果面对的是一张高清大图,非常大的可能出现OOM。但验证发现调整图片采样&#20540;只是降低了OOM发生的概率,没有从根本上根除问题。于是继续猜测分析找到了问题的根本原因。
MAT中的一些概念
outgoing references:被当前实例引用到的所有对象。通过它可以看出当前实例”抓着”哪些对象不放。incoming references:与outgoing references相反,表示引用到当前实例的所有对象。通过它可以看出谁在内存中”抓着”当前实例不放。Shallow size:对象本身占用内存的大小,不包含其引用的对象。Retained Heap:被一个对象所引用到的所有对象的Shallow size总和。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:321209次
积分:3816
积分:3816
排名:第6474名
原创:52篇
转载:300篇
评论:31条
(12)(18)(90)(3)(3)(16)(18)(3)(6)(10)(1)(2)(5)(5)(5)(28)(15)(7)(5)(10)(3)(6)(7)(1)(1)(4)(9)(7)(3)(1)(3)(2)(8)(15)(1)(23)}

我要回帖

更多关于 内存泄漏案例 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信