精华内容
下载资源
问答
  • 内存泄漏的代价是巨大的,并且通常直接与生产停机时间或部署进度表延迟有关。 不幸的是,适当的测试解决方案的成本也很高,而且客户通常不愿意-或无法-投资必要的资源。 需要明确的是,解决内存泄漏的最佳方法是在...

    cv::mat 内存泄漏

    内存泄漏的代价是巨大的,并且通常直接与生产停机时间或部署进度表延迟有关。 不幸的是,适当的测试解决方案的成本也很高,而且客户通常不愿意-或无法-投资必要的资源。

    需要明确的是,解决内存泄漏的最佳方法是在测试中检测并解决它们。 理想情况下,应该有一个测试计划,一个与生产环境相同的测试环境,该环境可以驱动代表性的和异常的工作负载,以及具有专门用于系统测试的技能的技术资源。 这是确保尽可能向生产过渡的最佳方法。 但是,设计和提供这种环境以及相关的文化变化并不是本文的重点。

    Java有四种常见的内存使用问题:

    • Java堆内存泄漏
    • 堆碎片
    • 内存资源不足
    • 本机内存泄漏。

    WebSphere Application Server引入了两种补充技术来帮助客户解决Java堆内存泄漏(在一定程度上,这些工具还解决了由内存资源不足引起的问题;本文未解决这两个问题的相似性)。

    通常,当应用程序无意(由于程序逻辑错误)保留对不再需要的对象的引用时,将导致Java堆内存泄漏。 这些无意的对象引用可防止内置的Java垃圾回收机制释放这些对象使用的内存。 这些内存泄漏的常见原因是:

    • 插入而不删除到集合中
    • 无限缓存
    • 未调用的侦听器方法
    • 无限循环
    • 会话对象过多
    • 编写得不好的自定义数据结构。

    由于配置问题或系统容量问题,可能导致由于内存资源不足而导致的内存使用问题。 例如,可以使用-Xmx参数将Java虚拟机允许的最大堆大小配置为一个太低的值,以致无法容纳内存中的用户会话总数。 或者,系统的物理内存可能太少,无法容纳当前的工作负载。 本机内存泄漏是非Java代码中的内存泄漏。 例如,在Type-II JDBC驱动程序中,或者在Java进程地址空间的非堆段中出现碎片。

    这是一篇介绍性文章,专为Java开发人员,系统管理员和问题确定顾问使用在IBM WebSphere Application Server上部署的应用程序而设计。

    检测内存泄漏根本原因的现有技术存在问题

    有许多问题会导致内存泄漏,这对于系统管理员来说尤其麻烦:

    1. 传统的Java内存泄漏根本原因检测技术具有很大的性能负担,可能不适合在生产环境中使用。 这些技术通常包括堆转储分析,附加JVMPI(Java虚拟机分析器接口)或JVMTI(Java虚拟机工具接口)代理,或使用字节码插入来跟踪集合中的插入和删除。

    2. 传统的冗余方法(例如群集)只能在一定程度上提供帮助。 内存泄漏将在整个群集成员中传播。 受影响的应用服务器的响应速度会减慢工作负载管理技术。 这可能导致将请求路由到更健康的服务器,并导致协调的应用程序服务器崩溃。

    典型的分析解决方案应尝试将应用程序移动到隔离的测试环境中,在该环境中可以重新创建问题,并且可以执行分析而不会影响生产服务器。 在这些测试环境中,复制内存的困难使关联的内存泄漏的成本增加了。

    这些问题的原因是传统技术试图同时进行检测和分析。

    WebSphere解决方案

    WebSphere Application Server V6.0.2和更高版本提供了一个分为两个阶段的解决方案,该解决方案将检测问题与分析分开。

    第一步是在生产WebSphere Application Server运行时中运行的轻量级内存泄漏检测机制。 这种轻量级的检测技术使用便宜的,通用的Java堆使用情况统计信息来监视内存使用情况趋势,并提早通知内存泄漏。 这使管理员有时间准备适当的备份解决方案,并以脱机方式分析问题的根本原因,而不会产生与测试环境中的复制相关的昂贵且令人沮丧的问题。

    解决方案的第二阶段是脱机工具:Java内存转储诊断(MDD4J),用于分析生产应用程序服务器外部的堆转储。 这是一个重量级的脱机内存泄漏分析工具,将多个现有的堆转储分析工具合并到一个用户界面中。

    为了缩小检测与分析之间的鸿沟,已为在IBM JDK上运行的WebSphere Application Server提供了自动堆转储生成工具。 在检测到内存泄漏模式后,此工具将生成多个堆转储,这些堆转储已与足够的内存泄漏进行了协调,以便于使用MDD4J进行比较分析。 另外,如果检测到OutOfMemoryError,则将IBM JDK配置为自动生成堆转储。 管理员应设置负载平衡,或在使用率较低时生成堆转储,以避免短期内性能下降。

    内存泄漏检测

    轻量级内存泄漏检测是通过监视空闲内存的下降趋势来实现的。

    泄漏可能非常快,也可能非常缓慢,因此可以分析短间隔和长间隔的内存使用趋势。 另外,分析了垃圾回收周期后内存使用率的下降趋势,以检测垃圾回收周期后平均可用内存低于某些阈值的情况。 这种情况要么是内存泄漏的迹象,要么是在资源太少的应用程序服务器上运行应用程序。 对于所有平台,此轻量级内存泄漏检测工具可从版本6.0开始在所有版本的WebSphere Application Server上使用。

    另外,专门针对iSeries®平台,iSeries上的WebSphere Application Server包括其他功能,用于检测Java堆大小是否将扩展到DASD上,并发出警报通知管理员即将发生这种情况,以及是否存在这种情况。或者是:

    • 有效内存池大小太小
    • 资源太少
    • 内存泄漏。

    zSeries®支持在6.0.2版中仅包含单个服务方拓扑,但在6.1版中进行了扩展以包括多个服务方拓扑。 单服务方拓扑将V6.0.2中的内存泄漏检测范围限制为问题确定或测试环境。

    图1显示了由内存泄漏检测功能生成的示例通知。 该通知是通过JMX发送的,显示在管理控制台中,并记录在服务器日志中。

    图1.示例内存泄漏通知
    内存泄漏通知示例

    自动堆转储生成工具(仅在IBM JDK上可用)在明显的内存泄漏迹象之后但在应用服务器由于OutOfMemoryError崩溃而生成堆转储。 足够的内存泄漏后,此功能会生成第二个堆转储。 这两个堆转储有助于使用MDD4J进行比较分析。

    自动堆转储生成可以默认启用,也可以在适当的时候使用MBean操作启动。

    WebSphere扩展部署

    尽管可以使用轻量级的内存泄漏检测,并且可以在WebSphere Application Server和WebSphere Application Server Network Deployment中立即使用,但它还是可以完全配置的,并且可以与高级自主管理器或定制JMX客户端进行交互。 WebSphere Extended Deployment是这种关系的一个示例。

    WebSphere Extended Deployment提供了许多配置内存泄漏检测的策略。 一种策略可能是通过对多个堆转储(使用工作负载管理来维护应用程序的性能)进行分析来对内存泄漏通知做出React。 另一个策略可能只是监视应用程序服务器的内存级别何时至关重要,以便在损坏之前重新启动应用程序服务器。

    内存泄漏分析

    一旦检测到内存泄漏并生成了堆转储,就可以将其转移到生产服务器之外,并进入问题确定机器进行分析。

    Java内存转储诊断(MDD4J)是一种脱机堆转储分析工具,可帮助确定内存泄漏的根本原因。 分析机制识别出怀疑的泄漏数据结构类和包。 通过此标识,系统管理员可以将内存泄漏的根本原因缩小到几个组件应用程序。

    WebSphere Application Server提供了用于托管第三方J2EE应用程序的容器。 随着Java堆栈的发展,随着时间的流逝,越来越多的抽象和组件化层被添加到基本Java应用程序和WebSphere Application Server堆栈中。 当发生内存泄漏时,这给确定问题提出了巨大的挑战。 对于遇到内存泄漏的系统管理员来说,托管许多第三方应用程序的WebSphere Application Server就像一个黑匣子。 在这种情况下,第一步是将内存泄漏的根本原因缩小到一个或几个组件。 根本原因通常在于一个错误的组件应用程序。 借助Java内存转储诊断工具的分析结果,系统管理员现在可以更快地识别出故障组件,而无需IBM的任何支持。

    一旦确定了故障组件,系统管理员就可以联系开发人员,他们可以在较小的环境中复制该问题,并在日志记录中使用调试器或特定的跟踪语句来识别故障的源代码方法,并进行必要的更改应用程序代码或配置来解决内存泄漏问题。 有时,了解故障组件或泄漏对象足以使我们确定一些常见的配置问题。 例如,找到许多HTTP会话对象将导致我们查看HTTP会话超时配置。

    该工具提供两种主要类型的分析功能:

    • 单个转储分析最常与内存转储一起使用,内存转储是由Java技术版的IBM Developer Kit(具有OutOfMemoryExceptions异常)自动触发的。 这种类型的分析使用启发式过程来识别具有包含大量子对象的容器对象的可疑数据结构。 这种启发式方法对于检测泄漏的Java集合对象非常有效,这些对象在内部使用数组存储所包含的对象。 已经发现,这种启发式方法在IBM支持部门处理的大量内存泄漏案例中是有效的。

    • 比较分析用于比较在内存泄漏应用程序的单次运行(即,空闲Java堆内存正在删除)期间执行的两个内存转储(主转储和基准转储)。 比较分析非常适合与轻量级内存泄漏检测结合使用。 为了进行比较分析,主转储是指在内存泄漏显着发展之后(消耗大量的最大已配置堆大小)进行的转储。 基线转储指的是由于内存泄漏尚未显着消耗堆的早期进行的堆转储。 转储之间的堆消耗越大,分析结果越好。

      比较分析技术可以识别出一组大型数据结构,这些结构在组成数据类型的实例数量上显着增长。 在每个转储中将数据结构分组在一起,然后在主要转储和基准转储中进行匹配和比较,以识别可疑的数据结构正在经历大量增长。 该技术与当今市场上许多分析工具中可用的基本堆转储差异技术不同,因为该技术以较高的粒度级别识别可疑泄漏数据结构,而不是识别可疑泄漏数据类型(例如,字符串的粒度要低得多)。 例如,MDD4J会告诉您某个特定的容器(例如特定的EJB对象)正在泄漏大量的字符串,而不是简单地告诉您从某个未知源泄漏了大量的字符串。 识别可疑数据结构有助于更好地了解内存泄漏的根本原因。 在图3的树视图中描述了一个示例数据结构,在其中可以看到不仅字符串对象正在泄漏,而且还从类中的HashSet以MyClass的名称引用了它们。

    分析结果显示在具有以下功能的基于Web的交互式用户界面中:

    • 列出分析结果,堆内容,大小和增长的摘要。

    • 列出可疑的数据结构,数据类型和程序包,这些数据结构有助于进行比较分析的堆使用量的增长,并有助于进行单个转储分析的堆的大容量。

    • 所有权上下文视图显示了主要足迹贡献者汇总集合中占足迹的主要贡献者与重要构成数据类型之间的所有权关系。

    • 交互式树视图中的浏览功能显示堆转储的相关部分,显示堆中所有可疑容器对象的所有传入(仅在树中显示一个引用,其余分别显示)和传出引用以及容器的子对象对象,根据到达范围排序。

    • 从可疑列表到所有权上下文,以及从内容视图到浏览视图的导航功能。

    • 具有过滤器和排序列的内存转储中所有对象和数据类型的列表视图。

    MDD4J工具结合了许多现有工具的最佳功能。 比较分析技术是基于Leakbot研究项目(参见相关主题 )。 单转储分析功能使用它也可以在HeapAnalyzer工具下载从alphaWorks(参见发现分析技术相关主题 )。 该表格的观点是基于从一个命令行工具的功能,HeapRoots(参见相关主题 )。

    使用WebSphere Application Server V6.1,用于Java的内存转储诊断(版本1.0)工具与IBM Support Assistant工具(版本3.0)打包在一起,IBM Support Assistant工具(系统管理员的独立工具)也可以从以下位置单独安装(请参阅参考资料 )。可以将堆转储转移到脱机分析的任何机器上的WebSphere Application Server。 (请参阅相关主题 ,了解如何从IBM Support Assistant的3.0版推出MDD4J 1.0版的信息。)

    下载IBM Support Assistant之后,可以使用Updater机制分别添加Java的Memory Dump Diagnostic:在IBM Support Assistant中,在Updater视图中选择New Products and Tools => Common Component and Tools (还请确保WebSphere还安装了Application Server V6.0或V5.0产品插件)。

    此新版本的MDD4J(版本1.0)具有许多与可伸缩性相关的改进,并且比以前的版本(版本0.97a)支持64位堆转储。 (可以将MDD4J的先前版本下载为developerWorks的技术预览;请参阅参考资料 。)

    以下示例显示了一个简单的内存泄漏Java应用程序的内存泄漏分析结果。 此示例应用程序将字符串对象泄漏到静态HashMap中。 轻量级泄漏检测能够在早期识别泄漏的存在并自动生成适当的堆转储。 然后使用Memory Dump Diagnostic for Java Version 1.0中的比较分析对这些转储进行脱机分析。 图2,图3和图4显示了显示可疑内存泄漏的分析结果。

    图2. Memory Dump Diagnostic for Java分析结果中的内存泄漏可疑对象
    Memory Dump Diagnostic for Java分析结果中的内存泄漏可疑对象
    图3.在Memory Dump Diagnostic for Java中浏览可疑的数据结构
    在Memory Dump Diagnostic for Java中浏览可疑的数据结构
    图4.用于Java分析的Memory Dump Diagnostic的占地面积分析
    用于Java分析的Memory Dump Diagnostic的占地面积分析

    结论

    借助WebSphere Application Server V6.1,系统管理员可以在生产环境中及早接收到有关内存泄漏的通知,而无需使用字节码插入或任何附加的代理。 轻量级内存泄漏检测旨在将对性能的影响降到最低,并提供在适当的时间自动生成堆转储的功能,以确保准确的分析结果。

    可以使用Java Memory Dump Diagnostic(MDD4J)离线分析手动生成的堆转储,或借助内存泄漏检测进行堆转储。 该工具可以帮助系统管理员将问题分配给适当的组件,以便开发人员可以在其计算机上轻松复制分析结果。 MDD4J为开发人员和问题确定顾问提供了一种工具,用于识别泄漏的候选对象,浏览堆和所有权链,以确定其代码中泄漏的数据结构。

    这项技术极大地有助于诊断Java堆内存泄漏检测和分析,并且可以与良好的系统测试过程或生产系统一起使用。

    致谢

    作者要感谢:Daniel Julin和Stan Cox审阅了这篇论文; LeakBot项目的首席研究员Nick Mitchell的领导能力和创新能力; Mark T Schleusner在开发MDD4J方面的协助与合作。


    翻译自: https://www.ibm.com/developerworks/websphere/library/techarticles/0606_poddar/0606_poddar.html

    cv::mat 内存泄漏

    展开全文
  • 使用MAT分析内存泄漏

    2020-01-10 09:20:05
    利用内存分析工具(Memory Analyzer Tool,MAT)分析java项目内存泄露 一、开发环境: 操作系统:ubuntu 14.04 IDE:Eclipse Java EE IDE for Web Developers.Version: Luna Service Release 2 (4.4.2) JDK版本...

    利用内存分析工具(Memory Analyzer Tool,MAT)分析java项目内存泄露

    一、开发环境:

    操作系统:ubuntu 14.04

    IDE:Eclipse Java EE IDE for Web Developers. Version: Luna Service Release 2 (4.4.2)

    JDK版本:1.7.0_80

    MAT版本:1.5.0

    二、事件起因

    最近通过公司的哨兵监控系统发现我的项目内存使用率每天都会增加一点,如下图。对于一个稳定运行的java项目而言,出现这种情况一般都有可能是出现了内存泄露。

    三、利用MAT检查内存泄露

    3.1 安装MAT

    MAT是有两种安装方式的,这一点与其他eclipse插件略有不同。

    一种安装方式是将MAT当做eclipse的插件进行安装:启动Eclipse --> Help --> Eclipse Marketplace,然后搜索Memory Analyzer,安装,重启eclipse即可。

    另外一种安装方式是将MAT作为一个独立的软件进行安装:去官网http://www.eclipse.org/mat/downloads.php,根据操作系统版本下载最新的MAT。下载后解压就可以运行了。

    我使用的是MAT 1.5 linux x64 版本。MAT不同版本按键位置或功能可能会有不同。

    3.2 修改MAT配置

    MAT 软件版本解压后目录内有个MemoryAnalyzer.ini文件,该文件里面有个Xmx参数,该参数表示最大内存占用量,默认为1024m,根据堆转储文件大小修改该参数即可。
    1. MemoryAnalyzer.ini中的参数一般默认为-vmargs– Xmx1024m,这就够用了。假如你机器的内存不大,改大该参数的值,会导致MemoryAnalyzer启动时,报错:Failed to create the Java Virtual Machine。
    2.当你导出的dump文件的大小大于你配置的1024m(说明1中,提到的配置:-vmargs– Xmx1024m),MAT输出分析报告的时候,会报错:An internal error occurred during: "Parsing heap dump from XXX”。适当调大说明1中的参数即可。

    3.3 获取堆转储文件

    获取堆转储文件我尝试过两种方式

    第一种方式是采用jamp获取,对于部署到服务器上的程序可以采用这种方式,获取堆转储文件后scp到本地,然后本地分析。获取命令为:

     

    [plain] view plain copy

     

    1. jmap -dump:format=b,file=<dumpfile.hprof> <pid>  

    这种方式获得的堆转储文件只能在MAT软件中打开,安装了插件的eclipse没有相应的打开选项。

    另一种方式是在eclipse中安装mat插件,运行程序,File --> new --> Other --> Heap Dump --> next ,选择对应的进程, Finish。这种方式似乎对远程服务器上的程序也可以,没有深入研究。此种方式获得堆转储文件后eclipse会默认打开,如下图所示,选择Leak Suspects Report, Finish就可以进入MAT分析页面的首页。

    3.4 堆转储文件分析

    我在实际操作过程中采用的是jmap获取堆转储文件,然后scp到本地,然后MAT软件加载。

    加载后首页如下图,在首页上比较有用的是Histogram和Leak Suspects。

    点击Leak Suspects会在堆转储文件同目录内生成一个Leak Suspects.zip文件,同时也会从首页跳转到Leak Suspects页面。

    解压该文件后可以通过浏览器打开分析结果。

    下面是Leak Suspects页面

    在Leak Suspects页面会给出可能的内存泄露,如上图所示有三个可能的内存泄露,但是只有第一个是我程序里的,另外两个是jar包或jdk里面的,这个可以不用管。

    点击Details进入详情页面。在详情页面Shortest Paths To the Accumulation Point表示GC root到内存消耗聚集点的最短路径,如果某个内存消耗聚集点有路径到达GC root,则该内存消耗聚集点不会被当做垃圾被回收。

    在All Accumulated Objects by Class列举了该对象所存储的所有内容。

    为了找到内存泄露,我获取了两个堆转储文件,两个文件获取时间间隔是一天(因为内存只是小幅度增长,短时间很难发现问题)。对比两个文件的对象,通过对比后的结果可以很方便定位内存泄露。

    MAT同时打开两个堆转储文件,分别打开Histogram,如下图。在下图中方框1按钮用于对比两个Histogram,对比后在方框2处选择Group By package,然后对比各对象的变化。不难发现heap3.hprof比heap6.hprof少了64个eventInfo对象,如果对代码比较熟悉的话想必这样一个结果是能够给程序员一定的启示的。而我也是根据这个启示差找到了最终内存泄露的位置。

    我内存泄露位置是一个list,这个list只在这里一直不停的往里添加eventInfo对象,却没有释放过。

    修改后代码:

     

    展开全文
  • opencv3和opencv4多线程内存泄漏问题:以cv::resize函数测试结果为例。 使用中可修复或者可避免内存泄漏:1)使用opencv2的版本;2)在代码中设置修复该问题.
  • 利用MAT进行内存泄露分析

    万次阅读 多人点赞 2015-12-13 23:31:40
    今天,就给大家简单介绍一下如何使用MAT(Memory Analyzer Tools)进行Android内存泄漏分析,目前这类文章已经很多,我只是抛砖引玉,希望大家能够将这个强大的工具灵活使用起来。 MAT简介 MAT是一款非常...

    前言

    对于程序员来说码代码容易,保证代码的稳定性很难。有时候写完一个功能可能只需要一天时间,但是这个功能隐藏的bug导致的线上问题排查可能需要一周或者更长时间。因此,拥有良好的代码结构和编码规范是一个程序员应该长期坚持并为之奋斗的一个目标。但是,百密也难免一疏,没有百分之百没有问题的代码,在产品上线前,我们需要对自己的代码进行充分的自测,发现问题解决问题,保证自己产品的稳定性并减少对用户的困扰。今天,就给大家简单介绍一下如何使用MAT(Memory Analyzer Tools)进行Android内存泄漏分析,目前这类文章已经很多,我只是抛砖引玉,希望大家能够将这个强大的工具灵活使用起来。

    MAT简介

    MAT是一款非常强大的内存分析工具,在Eclipse中有相应的插件,同时也有单独的安装包。在进行内存分析时,只要获得了反映当前设备内存映像的hprof文件,通过MAT打开就可以直观地看到当前的内存信息。一般说来,这些内存信息包含:

    • 所有的对象信息,包括对象实例、成员变量、存储于栈中的基本类型值和存储于堆中的其他对象的引用值。
    • 所有的类信息,包括classloader、类名称、父类、静态变量等
    • GCRoot到所有的这些对象的引用路径
    • 线程信息,包括线程的调用栈及此线程的线程局部变量(TLS)

    MAT支持将这些信息通过我们需要的方式归纳和显示,这样,整个内存信息就一览无余地显示在了我们的面前。
    虽然MAT有如此强大的功能,但是内存分析也没有简单到一键完成的程度,很多内存问题还是需要我们从MAT展现给我们的信息当中通过经验和直觉来判断才能发现。下面,我们就先介绍一下MAT的一些功能。

    Overview

    用MAT打开一个hprof文件后一般会进入如下的overview界面,或者和这个界面类似的leak suspect界面,overview界面会以饼图的方式显示当前消耗内存最多的几类对象,可以使我们对当前内存消耗有一个直观的印象。但是,除非你的程序内存泄漏特别明显或者你正好在生成hprof文件之前复现了程序的内存泄漏场景,你才可能通过这个界面猜到程序出问题的地方。
    overview

    Dorminator Tree(支配树)

    支配树可以直观地反映一个对象的retained heap,这里我们首先要了解两个概念,shallow heap和retained heap:

    • shallow heap:指的是某一个对象所占内存大小。
    • retained heap:指的是一个对象的retained set所包含对象所占内存的总大小。

    retained set指的是这个对象本身和他持有引用的对象和这些对象的retained set所占内存大小的总和,用官方的一张图来解释如下:

    retained set

    虽然说MAT定义的支配树和上图中的引用树稍有不同,但是二者的本质都可以反映一个对象如果被回收,有多少内存可以同时得到释放,只不过支配树的定义下可以更精确地反映这个数量。如果你感兴趣,可以继续看我之后的对支配树的概念解释,如果一时理解不过来可以跳过,把支配树简单想象成引用树即可。
    实际上支配树就是从引用树演化而来。所谓支配,例如x对象支配y对象,就是在引用树当中,一条到y的路径必然会经过x;所谓直接支配,例如x直接支配y,指的是在所有支配y的对象中,x是y最近的一个对象。支配树就是反映的这种直接支配关系,在支配树种,父节点必然是子节点的直接支配对象,下图就是一个从引用树到支配树的转换示意图:

    dorminator tree

    上面这幅图右边就是从左边转换而来形成的支配树,这里特别对C节点形成的子树做一下说明。CDE及DF、EG的关系就不用多说,从引用树和我们刚才对支配概念的阐述可以直接得出结论,关键是H节点,H节点在支配树种既不是D的子节点也不是E的子节点是因为,在引用树当中,能够到达H的路径有两条,DEFG节点都不是到达它的路径上必须经过的节点,符合这个条件的只有C节点,因此在支配树中H是C的子节点。现在仔细观察支配树可以发现,一个节点的子树正好就是这个节点对应的retained set。比如,如果D节点被回收,那么就可以释放DF对象所占的内存,但是H所占的内存还不能得到释放,因为可能G还在引用着H,这也是为什么在支配树中H是C节点的子节点的原因,只有C节点被回收了才一定能够回收H节点所占的内存。
    说了这么多,MAT中Dorminator Tree到底能够用到什么上面?我认为,它主要可以用于诊断一个对象所占内存为什么会不断膨胀,一个对象膨胀,就说明它对应到支配树中的子树就越来越庞大,只要分析这个对象对应的子树,确定那些对象是不应该出现在子树中就可以对问题手到病除。下面举一个实例进行简单分析。
    在android中ListView对象是可以不断对其子view进行服用来达到提高内存使用效率的目的,ListView的这一特性依赖于List Adapter的getView的实现,我们通过如下代码来故意使ListView无法服用其子view,从而模拟对象膨胀的情况,代码如下:

    public class LeakedListviewExample extends Activity {
        private ListView mLeakedListview;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.leaked_listview);
            mLeakedListview = (ListView) findViewById(R.id.leaked_listview);
            mLeakedListview.setAdapter(new LeakedAdapter());
        }
        private class LeakedAdapter extends BaseAdapter {
            @Override
            public int getCount() {
                return 1000;
            }
            @Override
            public Object getItem(int position) {
                return String.valueOf(position);
            }
            @Override
            public long getItemId(int position) {
                return position;
            }
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                // 不复用convertView,每次都new一个新的对象
                TextView leakedView = new TextView(LeakedListviewExample.this);
                leakedView.setText(String.valueOf(position));
                return leakedView;
            }
        }
    }   
    

    在运行代码的设备多次上下滑动这个ListView后,可以观察这个应用程序所占内存越来越大,每次手动GC后效果也不明显,说明出现了内存泄漏的现象,于是我们通过MAT dump出内存,切换到Dorminator Tree后可以看到如下的情况:

    leaked dorminator tree

    首先就可以看到ListView的shallow heap和retained heap是非常不成比例的,这说明这个ListView持有了大量对象的引用,正常情况下ListView是不会出现此现象。于是我们根据retained heap的大小逐步对ListView的支配树进行展开可以发现,Recycle Bin下面持有大量的TextView的引用,数量高达2500多个,而在正常情况下,ListView会复用Recycle Bin当中type类型相同的view,其中的对象个数不可能如此庞大,因此,从这里就可以断定ListView使用不当出现了内存泄漏。

    Histogram

    以直方图的方式来显示当前内存使用情况可能更加适合较为复杂的内存泄漏分析,它默认直接显示当前内存中各种类型对象的数量及这些对象的shallow heap和retained heap。结合MAT提供的不同显示方式,往往能够直接定位问题,还是以上面的代码为例,当我们切换到histogram视图下时,可以看到:

    histogram

    根据retained heap进行排序后可见,TextView存在2539个对象,消耗的内存也比较靠前,由于TextView属于布局中的组件,一般来说应用程序界面不能复杂到有这么多的TextView的存在,因此这其中必定存在问题。那么又如何来确定到底是谁在持有这些对象而导致内存没有被回收呢?有两种方式,第一就是右键选择List Objects -> with incoming reference,这可以列出所有持有这些TextView对象的引用路径,如图

    incoming reference

    从中可以看出几乎所有的TextView对象都被ListView持有,因此基本可以断定出现了ListView没有复用view的情况;第二种方式就更直接粗暴,但是非常有效果,那就是借助我们刚才说的dorminator tree,右键选择Immediate Dorminator后就可以看到如下结果

    immediate dorminator

    从中可以看到,有2508个TextView对象都被ListView的RecycleBin持有,原因自然就很明确了。

    MAT的查询选项

    上面主要介绍的是MAT的两个结果分析界面,其实,配合不同的查询选项,可以将它们的威力最大化。MAT中常用的查询选项集合都在顶部以图标的方式进行了显示

    queries tool bar

    从左到右依次为:显示Overview视图,显示Histogram视图,显示Dorminator Tree视图,使用OQL语言对结果进行查询,显示线程信息,结果智能分析,自定义查询选项集合,根据地址查询对象,结果分组,计算retained heap等。前三项就不再多说,分别对应之前我们介绍的三个结果分析结果显示方式,剩余的我挑选一些常用的选项结合一些实例进行说明。

    OQL(Object Query Language)

    在MAT中支持对象查询语句,这是一个类似于SQL语句的查询语言,能够用来查询当前内存中满足指定条件的所有的对象。OQL将内存中的每一个不同的类当做一个表,类的对象则是这表中的一条记录,每个对象的成员变量则对应着表中的一列。
    OQL的基本语法和SQL相似,语句的基本组成如下

    SELECT * FROM [ INSTANCEOF ] <class name="name"> [ WHERE <filter-expression> ] </filter-expression></class>
    

    所以,当你输入

    select * from instanceof android.app.Activity
    

    的时候,就会将当前内存中所有Activity及其之类都显示出来。但是OQL也有一些特别的用法。下面简单介绍一下OQL各部分的基本组成(更详细的可以参看这里)。

    FROM部分

    首先是OQL的from部分,指明了需要查询的对象,可以是如下的这些类型:

    • 一个类的完整名称,例如

      select * from com.example.leakdemo.LisenerLeakedActivity
      
    • 正则表达式,例如

      SELECT * FROM "java\.lang\..*"
      
    • 基本类型数组或者对象数组,例如int[]或者java.io.File[]

      select * from java.io.File[]
      
    • 对象的地址,例如0x2b7468c8

      select * from 0x2b7468c8, 0x2b7468dd
      
    • 对象的id,例如20815

      select * from 66888
      
    • 甚至可以是另外一个OQL的查询结果,以实现级联查询。

      SELECT * FROM (SELECT * FROM java.lang.Class c WHERE c implements org.eclipse.mat.snapshot.model.IClass )
      

    from部分还可以用如下两个关键词进行修饰

    • INSTANCEOF : 用来包含查询指定类的子类进行查询,否则只查询和from部分完全匹配的类,例如:

      SELECT * FROM INSTANCEOF android.app.Activity
      
    • OBJECTS : 修饰后最多只会返回一条结果,指明查询结果对应的类信息,而不是对象信息,例如:

      SELECT * FROM OBJECTS com.example.leakdemo.LisenerLeakedActivity
      

      返回的就是LisenerLeakedActivity对应的类的信息,而不是内存中存在的ListenerLeakedActivity对象。

    from部分还能为查询指定别名,以便提高可读性。例如

    SELECT result.mType FROM OBJECTS com.example.leakdemo.LisenerLeakedActivity result
    

    这里的result即是我们指定的别名,在我们不指定的情况下OQL默认别名为空,如下语句和上面语句效果是相同的

    SELECT .mType FROM OBJECTS com.example.leakdemo.LisenerLeakedActivity
    
    SELECT部分

    这部分和WHERE部分只最灵活也是最复杂的部分,但是大部分情况下我认为在这里使用“*”最好,因为一般这样我们的结果里面的信息是最全。不过如果你不想被结果中的其他信息干扰,可以自定义这部分的内容来实现。
    OQL可以通过自定义select来实现对内存中对象或者类的各种属性及方法结果等进行查询,这主要通过以下三种表达方式来实现。

    • 访问查询对象的属性的表达式为:

      [ <alias>. ] <field> . <field>. <field>
      

      其中alias代表别名,是在from部分中定义的,可以省略;field就是对象中定义的属性。这种方式能够访问到查询对象的属性值,例如:

      SELECT result.mType FROM OBJECTS com.example.leakdemo.LisenerLeakedActivity result
      

      能够展现出LisenerLeakedActivity.mType组成的查询结果。

    • 查询访问Java Bean及该对象在MAT中的属性,需要的表达式为:

      [ <alias>. ] @<attribute> 
      

      通过这种方式能够查询到的内容比较多,我们可以通过使用MAT中对OQL的自动补全功能(点这里查看)来选择需要查询的内容,例如:

      SELECT result.@clazz AS "class", result.@displayName AS "display name" FROM com.example.leakdemo.LisenerLeakedActivity result  
      

      就可以得到对象对应的class信息和对象在heap dump中的显示名称。

    • 查询访问一些底层对象的Java方法的返回结果。MAT可以通过反射来调用当前查询对象的底层类的Java方法,从而得到这些方法的执行结果。其表达式为:

      [ <alias> . ] @<method>( [ <expression>, <expression> ] ) ...
      

      注意method后面的括号是必不可少的,否则OQL会认为这是一个属性名称。”@”符号在新版本的OQL已经可以不需要了,例如:

      SELECT result.@clazz.hasSuperClass(), result.getField("mType") FROM com.example.leakdemo.LisenerLeakedActivity result 
      

      可以得到当前对象是否有基类,以及对象中的mType属性的值是多少。

    不仅如此,select部分还支持很多有用的内建函数,这里就不一一介绍了,大家可以戳这里查看。
    关于select部分的更多信息还可以到这里具体查看。

    WHERE部分

    灵活设置where参数可以为我们排除很大一部分的干扰信息,这部分对于属性和方法的支持和select部分相同,支持的运算符号和关键字有:

    <=, >=, >, <, [ NOT ] LIKE, [ NOT ] IN, IMPLEMENTS, =, !=, AND, OR

    相信大家都清楚这些关键值的意义,举一个简单例子

    SELECT * FROM com.example.leakdemo.LisenerLeakedActivity result where result.mType = 1
    

    上面那个查询语句可以查询到所有LisenerLeakedActivity.mType为1的对象。关于where部分的相信介绍可以参考这里
    OQL可以用来排查缓慢内存泄漏的情况,这种形式的内存泄漏只有程序长时间运行才会造成OOM,在排查阶段由于泄漏不严重而不能够在Dominator Tree或者Histogram中明显表现出来,但是如果你清楚在当前阶段程序中的一些重要对象的大概数量的话,则可以通过OQL来查询验证。这种方式对于在Android中排查Activity泄漏十分有用,例如有下面的代码:

    public class LisenerLeakedActivity extends Activity {
        private static final String TYPE_KEY = "type_key";
        private DemoListener mListener;
        private int mType = -1;
        public static void startListenerLeakedActivity(Context context, int actiivtyType) {
            Intent intent = new Intent(context, LisenerLeakedActivity.class);
            intent.putExtra(TYPE_KEY, actiivtyType);
            context.startActivity(intent);
        }
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            mType = getIntent().getIntExtra(TYPE_KEY, -1);
            mListener = new InnerListener();
            DemoListenerManager.getInstance().registerListener(mListener);
        }
        public int getActivityType() {
            return mType;
        }
        @Override
        protected void onDestroy() {
            super.onDestroy();
            // 故意制造泄漏
    //        if (null != mListener) {
    //            DemoListenerManager.getInstance().unregisterListener(mListener);
    //        }
        }
        private class InnerListener implements DemoListener {
            @Override
            public void nothingToHandle() {
                // nothing to do
            }
        }
    }
    

    上面Activity会在onCreate中向DemoListenerManager注册一个Listener,而这个Listener是Activity的内部类,因此它持有这个Activity的引用,如果在Activity的onDestroy阶段没有及时的反注册,那么这个Activity就泄漏了。现在随着多次启动和关闭这个Activity,我们可以发现gc后的内存总是不断增长,这是一个内存泄漏的特征,于是我们dump出当前内存,但是在其中的Dorminator Tree和Histogram中都不能很明确地得出问题所在,于是我们只能按照经验使用OQL来排查。假设你对Activity的泄漏有所怀疑,通过简单输入:

    select * from instanceof android.app.Activity
    

    可以得到如下结果:

    OQL

    从结果当中可以看到,当前内存中存在多个ListenerLeakActivity,而按照我们的原则,离开一个Activity就应该及时销毁和释放改Activity的资源,因此这就说明了Activity的泄漏是造成泄漏的原因之一。

    自定义查询选项集合

    这部分的功能其实和在显示列表中点击右键得到的菜单相同,如下所示:

    context menu

    下面挑选比较重要的几个选项来说明。

    List Objects

    有两个选项:with incoming refrences和with outcoming refreences,分别会罗列出选定对象在引用树中的父节点和子节点,可以作为为什么当前对象还保留在内存中的基本依据。在平时的排查过程中,此功能还是比较常用,因为可以顺着罗列出的引用路径一步步分析内存泄漏的具体原因,例如如下是上面Demo中针对ListenerLeakedActivity的一个with incoming reference的结果,从这个结果中可以逐步展开,最后得到是因为DemoListenerManager的单例仍然持有ListenerLeakedActivity的InnerListener造成泄漏的结论。

    List Objects

    注意,如果你选择with incoming references得到的每个结果展开后仍然是针对选择展开的内容的with incoming references,对于with outcoming references也一样。因此,如果两个对象存在循环引用,你需要仔细分析,排除循环引用的路径。例如上图中就不能再展开引用源:ListenerLeakedActivity的mListener进行分析,因为ListenerLeakedActivity本身就和InnerListner存在相互引用关系,展开就又得到了ListenerLeakedActivity的incoming references结果,和一级列表结果是一样的,一直这样展开下去就真是子子孙孙无穷匮也了。

    Show objects by class

    和List Objects相似,也可以罗列出当前选中对象在引用树种的上下级关系,只是这个选项会将结果中类型相同的对象进行归类,比如结果中有多个String类型的对象,List Objects会将它们分别显示出来,而Show objects by class会将它们统一归类为java.lang.String。虽然相对来说结果显示简洁了,但是我个人在平时分析的过程中一般较少使用到这个功能。

    Merge Shortest Path To GC Roots

    这是快速分析的一个常用功能,它能够从当前内存映像中找到一条指定对象所在的到GC Root的最短路径。这个功能还附带了其他几个选项,这几个选项分别指明了计算最短路径的时候是否是需要排除弱引用、软引用及影子引用等,一般来说这三种类型的引用都不会是造成内存泄漏的原因,因为JVM迟早是会回收只存在这三种引用的资源的,所以在dump内存映像之前我们都会手动触发一次gc,同时在找最短引用路径的时候也会选择上exclude all phantom/weak/soft etc. references选项,排除来自这三种引用的干扰。如下是针对ListenerLeakedActivity的一次Merge Shortest Path To GC Roots的结果,从图中可以明显地看到一条从sInstance->mListener->array->ListenerLeakedActivity的引用路径,这个结果已经足以定位Demo中内存泄漏的原因了。

    Merge Shortest Path To GC Roots

    Java Basics

    这个选项并不是很常用,这里只简单介绍一下其中的Class Loader Explorer子选项,它可以用来查看一些和选定对象的class loader相关的特性,包括这个class loader的名称、继承关系和它所加载的所有类型,如果你的程序当中应用到了自定义class loader或者osgi相关的特性,这个选项会为你分析问题提供一定程度的帮助。如下是我们通过Demo中的ListenerLeakedActivity的Class Loader Explorer可以看到如下结果。结果表明ListenerLeakedActivity是由PathClassLoader加载的,同时罗列了由这个class loader加载的其他8个类型以及这些类型目前在内存中的实例数量。

    Class Loader Explorer

    Java Collections

    这块的功能平时确实少有用到,对其中的功能应用不太了解,大家有兴趣可以参考这里。也欢迎大家帮助我补充一下这块的功能应用。

    Immidiate Dominators

    这是一个非常有用的功能,它的作用就是找出选择对象在Dominator Tree中的父节点。根据我们之前对Dominator Tree的阐述,就是找到一个对此对象存在于内存中负直接责任的对象,这个方法一般和List Objects或者Merge Shortest Path To GC Roots相互结合使用,共同定位或相互佐证。因为去掉了一个节点在引用树中的一个父节点并不一定能保证这个对象会被回收,但是如果去掉了它的Immediate Dominator,那这个对象一定会被回收掉;但同时,有时候一个对象的Immidate Dominator计算出来是GC Roots,因为它处在引用树中的路径没有一个非GC Roots的共同点,这个时候我们直接用Immidiate Dominators就不太好分析,而需要借助List Objects或者Merge Shortest Path To GC Roots的功能。因此,这几个方法需要相互配合使用。

    Show Retained Set

    这个功能简单来说就是显示选定对象对应在Dominator Tree中的子节点集合,所有这些子节点所占空间大小对应于它的retained heap。可以用来判断当一个对象被回收的话有多少其他类型的对象会被同时回收掉,例如,我们上面的Demo中,因为InnerListener同时被ListenerLeakedActivity和DemoListenerManager所持有,因此它不在这两个对象任何一个的retained set中,而是在这两个对象的公共节点GC Roots的retained set中,但是如果我们没有向DemoListenerManager中注册过InnerListener,那么它会出现在ListenerLeakedActivity的retained set中。

    结果分组

    这个选项支持将列表显示的结果按照继承关系、包名或者class loader进行分组,默认情况下我们结果显示是不分组的,例如Histogram和Dominator Tree的查询结果只是将对应引用树和Dominator Tree中的各个节点罗列出来,这样就包含了很多系统中的其他我们不关注的信息,使用分组的方法就可以方便聚焦到我们关注的内容之中。例如,下面分别是我们对Demo的Histogram视图结果进行按照包名和继承关系进行分组的结果显示。从中看到按照包名排序的方式方便我们着重关注那些我们自己实现的类在内存中的情况,因为毕竟一般情况下出现泄漏问题的是我们自己的代码;而按照继承关系的方式进行分类我们可以直接看到当前内存中到底有多少个Activity的子类及其对象存在,方便我们重点排查某些类。

    group by package
    group by superclass

    计算retained heap

    在我们一开始选择Histogram或者Dominator Tree视图查询结果的时候,MAT并没有立即开始为我们计算对应各个节点的Rtained Heap大小,此时我们可以用这个功能来进行计算,以方便我们进行泄漏的判断。这个选项又包含两个算法,一个是quick approx的方式,即快速估算,这个算法特点是速度快,能够快速计算出罗列各项Retained Heap所占的最小内存,所以你用这个方式计算后看到的Rtained Heap都是带有”>=”标志的;如果你想精确地了解各项对应Retained Heap的大小,可以选择使用Calculate Precise Retained Size,但是根据当前内存映像大小和复杂度的不同,计算过程耗时也不相同,但一般都比第一种方式慢得多。

    结束语

    还是花了不少时间来写这篇博客,中间参考了不少其他同行的劳动成果,也不知道是否后面能否完全一一列举,但更多的是自己的总结。在写这篇博客之前自己也使用过一段MAT,但并不是每个功能都使用到了,所以如果其中有什么地方理解有误还望各位读者留言指正。
    关于Android内存泄漏检测的一个神器LeakCanary,大家可以参考我之前写的另外一篇博客

    参考:

    展开全文
  • 内存泄漏基本概念 内存检测这部分,相关的知识有JVM虚拟机垃圾收集机制,类加载机制,内存模型等。编写没有内存泄漏的程序,对提高程序稳定性,提高用户体验具有重要的意义。因此,学习Java利用java编写程序的时候,...
  • Android 性能优化之使用MAT分析内存泄露问题

    万次阅读 多人点赞 2015-01-09 08:50:47
    我们平常在开发Android应用程序的时候,稍有不慎就有可能产生OOM,虽然JAVA有垃圾回收机,但也不能杜绝内存泄露内存溢出等问题,随着科技的进步,移动设备的内存也越来越大了,但由于Android设备的参差不齐,可能...

    转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/42396507),请尊重他人的辛勤劳动成果,谢谢!

    我们平常在开发Android应用程序的时候,稍有不慎就有可能产生OOM,虽然JAVA有垃圾回收机,但也不能杜绝内存泄露,内存溢出等问题,随着科技的进步,移动设备的内存也越来越大了,但由于Android设备的参差不齐,可能运行在这台设备好好的,运行在那台设备就报OOM,这些适配问题也是比较蛋疼的,比如我们平常运行着一个应用程序,运行的好好的,突然到某个Activity就给你爆出一个OOM的错误,你可能会以为是这个Activity导致的内存泄露,你会想到也有可能是内存有泄露吗?内存泄露就像一个定时炸弹,随时都有可能使我们的应用程序崩溃掉,所以作为一名Android开发人员,还是需要有分析内存泄露的能力,说道这里我们还是要说下什么是内存泄露,内存泄露是指有个引用指向一个不再被使用的对象,导致该对象不会被垃圾回收器回收。因此,垃圾回收器是无法回收内存泄露的对象。本文就使用DDMS(Dalvik Debug Monitor Server)和MAT(Memory Analyzer Tool)工具带大家来分析内存泄露问题。


    工具的准备

    DDMS是ADT自带的调试工具,有关DDMS的使用请参考http://developer.android.com/tools/debugging/ddms.html,而MAT的就需要我们自行安装Eclipse插件,安装方法我就不多说了,下面给出一个在线安装的地址:http://download.eclipse.org/mat/1.3/update-site/,MAT可以检测到内存泄露,降低内存消耗,它有着非常强大的解析堆内存空间dump能力。


    如何检测内存泄露

    1.使用DDMS检测内存泄露

    打开Devices视图,选择我们需要分析的应用程序进程,点击Updata Heap按钮


    然后在打开DDMS, 选择Heap标签,然后点击Cause GC按钮,点击Cause GC是手动触发JAVA垃圾回收器,如下图


    如果我们要测试某个Activity是否发生内存泄露,我们可以反复进入和退出这个Activity, 再手动触发几次垃圾回收,观察上图中 data object这一栏中的 Total Size的大小是保持稳定还是有明显的变大趋势,如果有明显的变大趋势就说明这个Activity存在内存泄露的问题,我们就需要在具体分析。


    2.使用Logcat检测内存泄露

    当垃圾回收机在进行垃圾回收之后,会在Logcat中作相对于的输出,所以我们也可以通过这些信息来判断是否存在内存泄露问题

    一,上面消息的第一个部分产生GC的原因,一共有四种类型
    GC_CONCURRENT   当你的堆内存快被用完的时候,就会触发这个GC回收
    GC_FOR_MALLOC  堆内存已经满了,同时又要试图分配新的内存,所以系统要回收内存
    GC_EXTERNAL_ALLOC   在Android3.0 (Honeycomb)以前,释放通过外部内存(比如在2.3以前,产生的Bitmap对象存储在Native Memory中)时产生。Android3.0和更高版本中不再有这种类型的内存分配了。
    GC_EXPLICIT  调用System.gc时产生,上图中就是点击Cause GC按钮手动触发垃圾回收器产生的log信息

    二,freed 1413K表示GC释放了1434K的内存

    三,20% free 9349K/11644K, 20%表示目前可分配内存占的比例,9349K表示当前活动对象所占内存,11644K表示Heap的大小

    四,paused 8ms + 3ms, total 71ms,则表示触发GC应用暂停的时间和GC总共消耗的时间

    有了这些log信息,我们就可以知道GC运行几次以后有没有成功释放出一些内存,如果分配出去的内存在持续增加,那么很明显存在内存泄露,如下存在内存泄露的Log信息

    很明显Heap中空闲内存占总Heap的比例在缩小,Heap中活动对象所占的内存在增加。


    内存泄露分析实战

    下面是一个存在内存泄露的例子代码,这也是比较常见的一种内存泄露的方式,就是在Activity中写一些内部类,并且这些内部类具有生命周期过长的现象

    package com.example.memoryleak;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import android.app.Activity;
    import android.os.Bundle;
    
    public class LeakActivity extends Activity {
    	private List<String> list = new ArrayList<String>();
    	
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		
    		//模拟Activity一些其他的对象
    		for(int i=0; i<10000;i++){
    			list.add("Memory Leak!");
    		}
    		
    		//开启线程
    		new MyThread().start();
    		
    	}
    	
    	
    	public class MyThread extends Thread{
    
    		@Override
    		public void run() {
    			super.run();
    			
    			//模拟耗时操作
    			try {
    				Thread.sleep(10 * 60 * 1000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			
    		}
    		
    		
    	}
    }
    
    运行例子代码,选择Devices视图,点击上面Updata Heap标签,然后再旋转屏幕,多重复几次,然后点击Dump HPROF file, 之后Eclipse的MAT插件会自动帮我们打开,如下图


    我们看到下面有Histogram(直方图)他列举了每个对象的统计,Dominator Tree(支配树)提供了程序中最占内存的对象的排列,这两个是我在排查内存泄露的时候用的最多的

    Histogram(直方图)

    我们先来看Histogram, MAT最有用的工具之一,它可以列出任意一个类的实例数。它支持使用正则表达式来查找某个特定的类,还可以计算出该类所有对象的保留堆最小值或者精确值, 我们可以通过正则表达式输入LeakActivity, 看到Histogram列出了与LeakActivity相关的类


    我们可以看到LeakActivity,和MyThread内部类都存在16个对象,虽然LeakActivity和MyThread存在那么多对象,但是到这里并不能让我们准确的判断这两个对象是否存在内存泄露问题, 选中com.example.memoryleak.LeakActivity,点击右键,如下图


    Merge Shortest Paths to GC Roots 可以查看一个对象到RC  Roots是否存在引用链相连接, 在JAVA中是通过可达性(Reachability Analysis)来判断对象是否存活,这个算法的基本思想是通过一系列的称谓"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走得路径称为引用链,当一个对象到GC Roots没有任何引用链相连则该对象被判定为可以被回收的对象,反之不能被回收,我们可以选择 exclude all phantom/weak/soft etc.references(排查虚引用/弱引用/软引用等)因为被虚引用/弱引用/软引用的对象可以直接被GC给回收.


    可以看到LeakActivity存在GC Roots链,即存在内存泄露问题,可以看到LeakActivity被MyThread的this$0持有。

    除了使用Merge Shortest Paths to GC Roots 我们还可以使用

    List object - With outgoing References   显示选中对象持有那些对象

    List object - With incoming References  显示选中对象被那些外部对象所持有

    Show object by class - With outgoing References  显示选中对象持有哪些对象, 这些对象按类合并在一起排序

    Show object by class - With incoming References  显示选中对象被哪些外部对象持有, 这些对象按类合并在一起排序


    Dominator Tree(支配树)

    它可以将所有对象按照Heap大小排序显示, 使用方法跟Histogram(直方图)差不多,在这里我就不做过多的介绍了


    我们知道上面的例子代码中我们知道内部类会持有外部类的引用,如果内部类的生命周期过长,会导致外部类内存泄露,那么你会问,我们应该怎么写那不会出现内存泄露的问题呢?既然内部类不行,我们就外部类或者static的内部类,如果我们需要用到外部类里面的一些东西,我们可以将外部类Weak Reference传递进去

    package com.example.memoryleak;
    
    import java.lang.ref.WeakReference;
    import java.util.ArrayList;
    import java.util.List;
    
    import android.app.Activity;
    import android.os.Bundle;
    
    public class LeakActivity extends Activity {
    	private List<String> list = new ArrayList<String>();
    	
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		
    		//模拟Activity一些其他的对象
    		for(int i=0; i<10000;i++){
    			list.add("Memory Leak!");
    		}
    		
    		//开启线程
    		new MyThread(this).start();
    		
    	}
    	
    	
    	public static class MyThread extends Thread{
    		private WeakReference<LeakActivity> mLeakActivityRef;
    		
    		public MyThread(LeakActivity activity){
    			mLeakActivityRef = new WeakReference<LeakActivity>(activity);
    		}
    
    		@Override
    		public void run() {
    			super.run();
    			
    			//模拟耗时操作
    			try {
    				Thread.sleep(10 * 60 * 1000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			
    			//如果需要使用LeakActivity,我们需要添加一个判断
    			LeakActivity activity = mLeakActivityRef.get();
    			if(activity != null){
    				//do something
    			}
    			
    		}
    		
    		
    	}
    }

    同理,Handler也存在同样的问题,比如下面的代码

    package com.example.memoryleak;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    
    public class LeakActivity extends Activity {
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		
    		MyHandler handler = new MyHandler();
    		handler.sendMessageDelayed(Message.obtain(), 10 * 60 * 1000);
    	}
    	
    	
    	public class MyHandler extends Handler{
    
    		@Override
    		public void handleMessage(Message msg) {
    			super.handleMessage(msg);
    		}
    	}
    		
    }
    我们知道使用MyHandler发送消息的时候,Message会被加入到主线程的MessageQueue里面,而每条Message的target会持有MyHandler对象,而MyHandler的this$0又会持有LeakActivity对象, 所以我们在旋转屏幕的时候,由于每条Message被延迟了 10分钟,所以必然会导致LeakActivity泄露,所以我们需要将代码进行修改下

    package com.example.memoryleak;
    
    import java.lang.ref.WeakReference;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    
    public class LeakActivity extends Activity {
    	MyHandler handler;
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		
    		handler = new MyHandler(this);
    		handler.sendMessageDelayed(Message.obtain(), 10 * 60 * 1000);
    	}
    	
    	
    	public static class MyHandler extends Handler{
    		public WeakReference<LeakActivity> mLeakActivityRef;
    		
    		public MyHandler(LeakActivity leakActivity) {
    			mLeakActivityRef = new WeakReference<LeakActivity>(leakActivity);
    		}
    
    		@Override
    		public void handleMessage(Message msg) {
    			super.handleMessage(msg);
    			
    			if(mLeakActivityRef.get() != null){
    				//do something
    			}
    		}
    	}
    
    
    	@Override
    	protected void onDestroy() {
    		handler.removeCallbacksAndMessages(null);
    		super.onDestroy();
    	}
    		
    }

    上面的代码就能保证LeakActivity不会被泄露,注意我们在Activity的onDestory方法中使用了handler.removeCallbacksAndMessages(null),这样子能保证LeakActivity退出的时候,每条Message的target  MyHandler也会被释放, 所以我们在使用非static的内部类的时候,要注意该内部类的生命周期是否比外部类要长,如果是的话我们可以使用上面的解决方法。


    常见的内存泄露问题

    1.上面两种情形

    2.资源对象没有关闭,比如数据库操作中得Cursor,IO操作的对象

    3.调用了registerReceiver注册广播后未调用unregisterReceiver()来取消

    4.调用了View.getViewTreeObserver().addOnXXXListener ,而没有调用View.getViewTreeObserver().removeXXXListener

    5.Android 3.0以前,没有对不在使用的Bitmap调用recycle(),当然在Android 3.0以后就不需要了,更详细的请查看http://blog.csdn.net/xiaanming/article/details/41084843

    6.Context的泄露,比如我们在单例类中使用Context对象,如下

    import android.content.Context;
    
    public class Singleton {
    	private Context context;
    	private static Singleton mSingleton;
    	
    	private Singleton(Context context){
    		this.context = context;
    	}
    	
    	public static Singleton getInstance(Context context){
    		if(mSingleton == null){
    			synchronized (Singleton.class) {
    				if(mSingleton == null){
    					mSingleton = new Singleton(context);
    				}
    			}
    		}
    		
    		return mSingleton;
    	}
    
    }

    假如我们在某个Activity中使用Singleton.getInstance(this)或者该实例,那么会造成该Activity一直被Singleton对象引用着,所以这时候我们应该使用getApplicationContext()来代替Activity的Context,getApplicationContext()获取的Context是一个全局的对象,所以这样就避免了内存泄露。相同的还有将Context成员设置为static也会导致内存泄露问题。

    7.不要重写finalize()方法,我们有时候可能会在某个对象被回收前去释放一些资源,可能会在finalize()方法中去做,但是实现了finalize的对象,创建和回收的过程都更耗时。创建时,会新建一个额外的Finalizer 对象指向新创建的对象。 而回收时,至少需要经过两次GC,第一次GC检测到对象只有被Finalizer引用,将这个对象放入 Finalizer.ReferenceQueue 此时,因为Finalizer的引用,对象还无法被GC,
    Finalizer$FinalizerThread 会不停的清理Queue的对象,remove掉当前元素,并执行对象的finalize方法,清理后对象没有任何引用,在下一次GC被回收,所以说该对象存活时间更久,导致内存泄露。

    如果大家还有一些内存泄露的情形欢迎提出来,我好更新下!









    展开全文
  • 一、开发环境: linux3.10.0-327.el7.x86_64 weblgoic:10.3.6 应用环境JDK版本:java version "1.6.0_45" 本地主机JDK版本:java version "1.8.0_211" MAT版本:Eclipse Memory Analyzer Version...二、内存泄...
  • 我们可以使用 jmap –histo 这种命令去分析哪些对象占据着...MAT 工具是基于 Eclipse 平台开发的,本身是一个 Java 程序,是一款很好的内存分析工具,所以如果你的堆快照比较大的话,则需要一台内存比较大的分析机器,
  • 利用mat定位内存泄露原因

    千次阅读 2016-03-07 11:05:40
    MAT分析: histogram--shallow heap排序-list objects-withincoming references--gc root,非常好定位
  • 玩转MAT分析内存泄漏

    2020-09-05 16:45:36
    MAT 工具是基于 Eclipse 平台开发的,本身是一个 Java 程序,是一款很好的内存分析工具,所以如果你的堆快照比较大的话,则需要一台内存比较大的分析机器,并给 MAT 本身加大初始内存,这个可以修改安装目录中的 ...
  • 之前看到过说opencv做了完善的内存管理机制,因此在使用opencv时不用手动释放内存,但是今天使用的时候还是产生了内存泄漏,代码的功能是循环读取图片,做颜色转换,然后做其他操作,代码如下: for(xxx){ cv::Mat ...
  • Eclipse MAT 检测内存泄露

    千次阅读 2016-01-12 13:41:50
    仔细看了看,网上关于mat使用的资料比较少。尤其是中文的,基本上没有系统完整性的文档。本文主要列出提纲,给出有用的链接,开启思路。也会不断的补充case。 一,安装  下载地址:...
  • GC Roots,从一个对象到 GC Roots 的引用链被称为 Path to GC Roots, 通过分析 Path to GC Roots 可以找出 JAVA 的内存泄露问题,当程序不在访问该对象时仍存在到该对象的引用路径(这个对象可能内存泄漏)。...
  • 利用MAT,两步找出内存泄漏原因

    千次阅读 2017-01-02 21:18:05
    当我们利用 Android Studio 的 Memory Monitor 工具(HPROF Viewer 和 Analyzer)找出内存泄漏的Activity 后,你会发现这么多的引用,要找出泄漏点太难了,同时AS尚未提供有效的工具(可能我不知道,求告知),那么...
  • Java内存 Java管理的内存分两种, 堆和栈. 栈是保存函数形参和局部变量的地方, 栈里保存的是对象的引用. 一个正在执行的函数总是存放在栈的最 上层.每个线程都有自己的调用栈. 堆上存放着所有java程序通过”new”...
  • 我们都知道OpenCV中存储图像常用的方法就是用Mat表示. 基本上讲 Mat 是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)和一个指向存储所有像素值的矩阵(根据所选存储方法的不同...
  • 1.Java内存分配策略 Java 程序运行时的内存分配策略有三种:静态分配、栈式分配和堆式分配。对应的存储区域如下: 静态存储区(方法区):主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经...
  • 内存泄漏排查 MAT工具使用

    千次阅读 2018-05-06 11:00:48
    很多时候我们使用系统的IO流,Cursor,Receiver如果不及时释放,就会导致内存泄漏,这些场景是常见的,一般开发人员也都能够避免。但是,很多时候内存泄漏的现象不是很明显,1.比如内部类,Handler相关的使用导致的...
  • 内存泄漏是一些已经不使用的对象,依然占有内存且垃圾回收机制不法回收它们。最终导致 常驻内存越来越大 ,影响到程序的性能。 在Android 虚拟机中,采用Mark-Sweep方式实现垃圾回收。Mark标记,Sweep检测。...
  • 对于内存泄漏,在Android中如果不注意的话,还是很容易出现的,尤其是在Activity中,比较容易出现,下面我就说下自己是如何查找内存泄露的。 首先什么是内存泄漏内存泄漏就是一些已经不使用的对象还存在于...
  • 明显是 YCYX-Task 这个线程出现了内存溢出,导致程序假死。 同时查看当前应用内存占用已达到4G: 二、排查历程 1、初步定位 jinfo 首先,我们使用jinfo pid查看当前jvm的堆相关参数: 可见,最大堆容量为:4G。 ...
  • 对于内存泄漏,在Android中如果不注意的话,还是很容易出现的,尤其是在Activity中,比较容易出现,下面我就说下自己是如何查找内存泄露的。 首先什么是内存泄漏内存泄漏就是一些已经不使用的对象还存在于内存之...
  • Android Studio +MAT 分析内存泄漏实战

    万次阅读 2016-07-29 20:45:10
    对于内存泄漏,在Android中如果不注意的话,还是很容易出现的,尤其是在Activity中,比较容易出现,下面我就说下自己是如何查找内存泄露的。首先什么是内存泄漏内存泄漏就是一些已经不使用的对象还存在于内存之中...
  • 使用MAT分析内存泄露步骤

    千次阅读 2014-04-17 16:26:18
    项目优化过程中,总会遇到内存的一些问题,即便前期设计很优秀,可有可能在编码过程中留下内存泄露等问题,而一般查找内存问题是比较困难的,而使用MAT工具,结合项目代码及个人经验,能够比较快速的定位问题并解决...
  • 导出java进程的堆内存信息 使用下面命令导出java进程的堆内存,其中pid为java进程id,...这步主要说一点,如果heap.bin很大MAT可能不够内存加载文件,可以修改配置文件加大MAT内存,我的是1.9.0版本,修改配置文件M...
  • 首先我们要学会如何生成hprof文件 ...3.进行可能发生内存泄漏的操作 4.单击Dump HPROP File 按钮结束追踪,生成并保存hprof文件 DDNS生成的hporf文件并不是标准的,还需元京塔转换成为标准的hprof文件才能被MA...
  • MAT内存泄露分析

    2014-02-19 15:40:51
    几个还不错的例子,学会了如何定位java的内存泄露问题,如下: 例1、 http://tivan.iteye.com/blog/1487855 例2、 http://blog.csdn.net/knowledgeaaa/article/details/16903241 ...
  • 使用MAT分析内存泄漏(一)

    千次阅读 2012-06-02 10:16:47
    前言   在平时工作过程中,有时会遇到OutOfMemoryError,我们知道...现在向大家引荐Eclipse Memory Analyzer tool(MAT),来化解我们遇到的难题。   为什么用MAT   之前的观点,我认为使用实时profiling/monit

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,204
精华内容 5,681
关键字:

mat内存泄漏