• 1. 内存泄露研究大纲1、需求背景 2、预期目标原则 3、内存管理原理与内存泄漏方式 4、内存管理程序开发规范建议 5、内存泄漏的检测方式 6、下一步工作建议 7、待技委会审核事项
  • 2. 需求背景对于典型的开放平台应用(基于Java或C开发),希望能够: 第一、帮助开发及测试人员在开发、测试的过程学会使用工具来定位应用有关内存问题源头,减少应用上线后出问题; 第二、必要时,能够做到对测试或生产环境中的应用进行实时监控,了解目前系统是否存在内存泄露,进行提前处理; 第三、对测试或生产环境已经出现的内存问题,通过收集相关系统信息进行事后分析,找到引起系统性能下降或崩溃的原因,避免问题再次出现。
  • 3. 预期目标使我们的开发测试人员了解Java及C语言的内存管理原理,了解内存泄漏的原因及分类。 结合业界相关内存泄漏检测工具,通过事前检测、事中检测、事后核查几个环节,掌握对Java及C内存泄漏分析的基本步骤及分析定位方法。 做到对应用中出现的内存泄露、数组越界等情况做到开发规范上的预防,提早检测,提早定位,提早修改。
  • 4. 内存管理的原理与内存泄漏方式 Java与C语言的内存管理模型与内存泄漏定义 内存泄漏方式(频率、类型)
  • 5. 内存管理的原理与内存泄漏方式 -Java内存模型 图3 JVM的体系结构 1、不同虚拟机有不同的GC算法实现; 2、对象、实例、数组都是在Java堆上分配。动态内存的生存期(生命周期)由编码的程序员决定。堆上的内存泄露是Java内存泄露最主要来源。 GC 是Java 虚拟机的内存管理者,因此GC 除了负责回收垃圾外还负责分配内存。
  • 6. 内存管理的原理与内存泄漏方式 -C内存模型
  • 7. 内存管理的原理与内存泄漏方式 -JAVA内存管理(有向图管理)
  • 8. 内存管理与内存泄漏-内存泄漏√在JAVA中,内存泄漏定义:这些对象可达;但这些对象是无用的,即发生内存泄露。 √在C++中,内存泄漏的范围更大一些。这些对象被分配了内存空间,却不可达,由于C++中没有GC,这些内存在主程序结束前无法正常回收。
  • 9. 内存管理的原理与内存泄漏方式-泄漏方式 对于常见的内存泄漏问题,若以发生的方式来分类,可以分为4类: 1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次都会导致一块内存泄漏。 2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。 3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅有一块内存发生泄漏。比如,在Singleton类型的类构造函数中分配内存,在析构函数中却没有释放该内存,但是因为这个类是一个Singleton类(一个类只有一个实例),所以内存泄漏只会发生一次。 4. 隐式内存泄漏。如果内存管理不当,在程序运行过程分配的内存,直到主程序结束时才释放。例如,一个服务端程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存,此种情况可以定义为隐式泄漏。 从用户使用程序的角度来看,内存泄漏本身一般不会产生什么危害,真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。
  • 10. 内存管理的原理与内存泄漏方式-泄露类型向集合中插入而没有删除 对象使用结束后,对象游离 触发了无限循环,数据得不到回收,导致数据OutOfMemory 未能正确清除登录会话导致过多的会话对象 未绑定的缓存 未调用的侦听器方法 本机内存泄漏
  • 11. 内存管理程序开发规范建议-Java语言 JAVA语言编码注意事项 最基本的建议是尽早释放无用对象的引用。如: A a = new A(); {使用a对象} a = null; //当使用对象a之后主动将其设置为空,以便在下次GC时能及时回收这部分内存 尽量少用finalize函数,它会加大GC的工作量; 少用并小心使用集合数据类型,包括树、图、链表等数据结构,这些数据结构对GC来说,回收更为复杂; 尽量避免强制系统做垃圾内存的回收,增长系统做垃圾回收的最终时间; 只要有可能,尽量使用基本变量类型,而不使用对象类型。例如,使用 int,而不使用 Integer。
  • 12. 内存管理程序开发规范建议-Java语言 缓存那些频繁使用的寿命短的对象,避免一遍又一遍地重复创建相同的对象,并因此加重垃圾收集的负担。例如:对经常使用的图片,可以使用soft引用类型。这种类型可以尽可能将图片缓存在内存中,供程序调用,而不引起Out Of Memory。 关注本地方法类的使用,避免由于本地方法类的使用导致内存泄露的发生。Java Native Interface(JNI)使用本机代码编写应用程序中,如数据库连接资源的未及时释放或者其他Java在本地代码中资源未正确释放引用都会导致进程的地址空间中的非堆段内的碎片。 在处理 String 的时候要尽量使用 StringBuffer 类,StringBuffer 类是构成 String 类的基础。String类将StringBuffer 类封装了起来,(以花费更多时间为代价)为开发人员提供了一个安全的接口。当我们在构造字符串时,应该用 StringBuffer 来实现大部分的工作,当工作完成后将 StringBuffer 对象再转换为需要的 String 对象。比如:如果有一个字符串必须不断地在其后添加许多字符来完成构造,那么我们应该使用 StringBuffer 对象和它的 append() 方法。如果我们用 String 对象代替 StringBuffer 对象的话,会花费许多不必要的创建和释放对象的过程,导致内存和cpu使用频繁。
  • 13. 内存管理程序开发规范建议-Java语言及时清除不再需要的会话。 为了清除不再活动的会话,许多应用服务器都有默认的会话超时时间,一般为30分钟。当应用服务器需要保存更多会话时,如果内存容量不足,操作系统会把部分内存数据转移到磁盘,应用服务器也可能根据“最近最频繁使用”(Most Recently Used)算法把部分不活跃的会话转储到磁盘,甚至可能抛出“内存不足”异常。在大规模系统中,串行化会话的代价是很昂贵的。当会话不再需要时,应当及时调用HttpSession.invalidate()方法清除会话。
  • 14. 内存管理程序开发规范建议-C语言 C语言编码注意事项 用malloc或new申请内存之后,必须有对应的free或delete匹配!并注意在各代码分支中避免free的遗漏。 用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。 用free或delete释放了内存之后,应立即将指针设置为NULL,防止产生“野指针” ,以免带来内存泄露的风险; C语言中的malloc/free或C++语言中的new/delete操作非常耗时,而且依赖于程序员释放,往往是内存泄露的基本根源。因此要尽可能优先考虑从线程栈中获得内存,优先考虑栈而减少从动态堆中申请内存,不仅仅是因为在堆中开辟内存比在栈中要慢很多;而且有助于减少内存泄露的可能。 对应的new和delete要采用相同的形式,如果你调用new时用了[],调用delete时也要用[]。
  • 15. 内存管理程序开发规范建议-C语言建议在析构函数里对指针成员调用delete,因为如果在析构函数里没有删除指针,它不会表现出很明显的外部症状。相反,它可能只是表现为一点微小的内存泄露,并且不断增长,最后吞噬了你的地址空间,导致程序最终崩溃。因为这种情况经常不那么引人注意,所以每增加一个指针成员到类里时一定要记清楚。 应用程序尽量选选用现有的基础性库代码,尤其像复杂的数据结构。如线性列表类list等,以降低自己编写相关库代码带来的内存泄露和性能管理的风险。 不要忘记为数组和动态内存赋初值,以防止将未被初始化的内存被误以使用,从而导致内存使用的不可控。 避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。有效控制指针越界带来的内存使用风险。 避免非常大的内存分配申请,当必须申请大内存对象时,建议分多次分段申请。有时候内存问题不是由当时的堆状态造成的,而是因为分配失败造成的。由于大内存块必须是是连续的,而随着堆越来越满,找到较大的连续块越来越困难。(此建议对java语言同样有效)
  • 16. 内存泄漏的检测方式按检测时点区分为: 内存泄漏的事前检测(版本投产前) 内存泄漏的事中监控(版本运行中) 内存泄漏的事后排查(问题出现后)
  • 17. 内存泄漏的检测方式-事前检测事前检测分:静态代码分析和动态运行数据收集分析静态代码分析 Java代码的静态分析工具,经过我们对比验证后,认为lint4j(jutils.com公司), pmd,findbug,可以实现Java代码的语法分析。可以检查出比如:使用未初始化变量,检查函数返回值是否合规,空的try/catch代码块等语法错误。其中findbug(FindBugs requires JRE (or JDK) 1.5.0 or later to run. )可以直接共享使用。 对于C或C++代码的静态检测,可以使用AIX或Soloris下面的自带的C语法分析工具:lint,不但能发现标准C语言中的语法错误,而且也能指出如“没有赋值之前自动使用参数”,“错误使用结构指针的代码”,“没有被使用的变量或函数”
  • 18. 内存泄漏的检测方式-事前检测动态运行数据收集分析 针对c的内存使用,我部自行开发了ICBCMemCheck工具进行动态运行数据收集和分析: 在C中内存的分配与回收是通过对malloc,remalloc以及free函数的跟踪记录实现(C++中类似,用new及delete)。 使用该工具,可以记录下源代码中调用内存分配函数的C源文件名以及在源文件中所处的位置,同时也能检测出数组的地址越界。 在Windows下使用方法是:通过引入一个库函数,并在被检测程序的主函数的起始和结束位置添加辅助代码实现,调试通过后再去除辅助代码后重新编译发布。 在Unix下,该检查工具是非侵入式的,即在编译阶段时,只要将程序在编译时候带上调试参数-g,被检测程序不需要导入包或修改源代码。在最终版本发布时,重新不带-g编译即可。
  • 19. 内存泄漏的检测方式-事中监控 事中检测,WAS本身提供了一系列针对内存问题的探测和分析诊断工具,这些工具可以帮助用户进行内存问题的诊断: 一方面可以在系统在发生OutOfMemory错误之前,用户可以在无须进行复杂分析的条件下,预知在其部署的应用中是否存在内存泄漏的问题。 另一方面如果确有内存泄漏现象发生,WAS还提供了相应的工具,可以帮助用户进行分析诊断,从而找到内存泄漏的真正原因。 下面分别介绍一些事中检测的工具说明:
  • 20. 内存泄漏的检测方式-事中监控1、Websphere性能诊断顾问:分析 PMI 数据(如连接池大小、已使用内存、空闲内存数据)并提供关于性能调整的建议。属于轻量级内存泄漏检测机制,提供内存泄漏的早期通知,即通过监测垃圾收集周期之后可用内存使用中的是否下降趋势以及平均可用内存数低于特定门限的情况,提前发出警告。 2、使用TPV实时监视JVM的状况:包括JVM内存使用情况(FreeMemory,UsedMemory ,JVM 运行时HeapSize等)。TPV图形理想情况下应该是锯齿状,图形中每个坡(下降)对应着一次内存的垃圾回收(Garbage collection),如果监控过程中出现如下情况,则有可能发生了内存泄漏: -每次垃圾回收后的已使用内存的数值骤增。 -TPV对应的已使用内存图形更接近于阶梯(staircase),或者锯齿形状严重不规则。 -还可以查看分配的对象数与释放的对象数之差值,如果这个数值越来越大,则有内存泄漏(如果需要查看对象数,需要启用JVMTI接口并在PMI中启用相应的JVM计数器)。
  • 21. 事后排查,一般是针对已经出现明显内存异常的应用,打开系统的跟踪日志或详细垃圾回收日志,收集诸如 GC文件native_stderr.log、 heapdump及javacore文件等故障信息、利用相应工具进行离线分析。 内存泄漏的检测方式-事后排查
  • 22. 内存泄漏的检测方式-事后排查对GC文件(native_stderr.log)分析: IBM Pattern Modeling and Analysis Tool for Java Garbage Collector(PMAT)工具进行gc文件native_stderr.log分析。 PMAT工具解析JAVA SDK的内存回收(GC)日志,并提供统计信息,图表,推荐Java堆配置。
  • 23. 内存泄漏的检测方式-事后排查对heapdump文件分析: 有两种分析工具HeapAnalyzer 和MDD4J(Memory Dump Diagnostic for Java)各有各得侧重点。 HeapAnalyzer 在对heapdump文件分析时会生成建议的KCluster的大小、根对象的个数,总内存大小等建议; 其中Gap分析可以看到目前内存中的碎片大小系统中剩余内存块的个数以及剩余内存的大小。 通过对内存对象树可以看到经过HeapAnalyzer系统分析后最大怀疑内存泄漏对象,以及对象父节点及儿子节点,根据节点中的类路径可以基本定位出现内存泄漏源代码位置。 对于怀疑是由于大对象申请导致内存错误OOM进行分析,协查应用可能的内存泄漏对象以及对象源代码包的位置。 MDD4J (Memory Dump Diagnostic for Java) 该工具通过对虚拟机(JVM)生成的Heap Dump文件处理,然后解析成对象、类的内存信息,并基于解析的信息创建对象图和对象树,列出怀疑有内存泄漏的对象集合。(对于某些应用,堆大小的默认设置可能不能得到最优的性能。该工具可以提供JAVA堆参数的设置建议。 )
  • 24. 内存泄漏的检测方式-事后排查对JavaCore文件分析: Thread Analyzer能够分析线程的挂起状态,包括死锁、死循环等情况,还能对多次生成的Javacore文件进行比较分析。它是IBM专门用来分析JavaCore文件的,通过使用Thread Analyzer能够显示javacore文件的原因(Signal,OOM等)、系统设置的最大堆大小、最小堆大小等信息显示了所有的进程信息,以及当前的进程运行状态,例如正常线程、等待线程、阻塞线程、死锁线程等,以及哪些方法占了大量的CPU时间。 该工具较适合分析压力测试或者生产环境中,应用出现响应缓慢的原因,并且找出消耗CPU时间的应用方法名称,应用中是否出现死锁情况的判断。
  • 25. 内存泄漏的检测方式-事后排查定位应用中的大对象 用户在使用WAS运行自己应用的时候经常会碰到OOM问题。其中很大一部分情况是应用程序分配大对象造成的Java堆空间碎片问题。因此,如何快捷准确的找到分配大对象的方法并加以改进是解决这类问题的关键。 IBM提供了一个分析工具profile,该工具提供了定位WAS分配大对象的方法,需要注意的是该工具的启用可能会消耗大量的CPU计算资源,所以建议在测试环境中使用。 在设置了该分析工具后,在详细垃圾回收信息中将会出现下面红框所示的信息: 根据输出的信息判断应用所申请的最大对象的大小,同时根据HeapAnalyzer分析工具输出的内存碎片大小就可以判断是否是因为大对象申请导致的OOM问题。
  • 26. 内存泄漏的检测方式-工具清单
  • 27. 7、待技委会审核事项1、对内存泄漏的检测方式-工具清单所列工具在全开发中心内部推广,建议先找一个开发部门、一个测试部门进行试点、试用; 2、我们在第四节中提出的代码开发建议是否补充纳入到开发中心的现有的技术规范《 》中。参见4、内存管理程序开发规范建议 待实施内容和参与部门建议。