资源预览内容
第1页 / 共5页
第2页 / 共5页
第3页 / 共5页
第4页 / 共5页
第5页 / 共5页
亲,该文档总共5页全部预览完了,如果喜欢就下载吧!
资源描述
性能测试总结之内存泄露和内存溢出2009-12-10 作者:yunshuai 来源:Taobao QA Team 刚刚做完了一个项目的性能测试,“有幸” 也遇到了内存泄露的案例,所以在此和大家分享一下。主要从以下几部分来说明,关于内存和内存泄露、溢出的概念,区分内存泄露和内存溢出;内存的区域划分,了解 GC 回收机制;重点关注如何去监控和发现内存问题;此外分析出问题还要如何解决内存问题。下面就开始本篇的内容:第一部分 概念众所周知,java 中的内存 java 虚拟机自己去管理的,他不想 C+需要自己去释放。笼统地去讲,java 的内存分配分为两个部分,一个是数据堆,一个是栈。程序在运行的时候一般分配数据堆,把局部的临时的变量都放进去,生命周期和进程有关系。但是如果程序员声明了 static 的变量,就直接在栈中运行的,进程销毁了,不一定会销毁 static 变量。另外为了保证 java 内存不会溢出, java 中有垃圾回收机制。 System.gc()即垃圾收集机制是指 jvm 用于释放那些不再使用的对象所占用的内存。java 语言并不要求 jvm 有 gc,也没有规定 gc 如何工作。垃圾收集的目的在于清除不再使用的对象。gc 通过确定对象是否被活动对象引用来确定是否收集该对象。而其中,内存溢出就是你要求分配的 java 虚拟机内存超出了系统能给你的,系统不能满足需求,于是产生溢出。内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问 ,该块已分配出来的内存也无法再使用,随着服务器内存的不断消耗,而无法使用的内存越来越多,系统也不能再次将它分配给需要的程序,产生泄露。一直下去,程序也逐渐无内存使用,就会溢出。第二部分 原理JAVA 垃圾回收及对内存区划分在 Java 虚拟机规范中,提及了如下几种类型的内存空间: 栈内存(Stack):每个线程私有的。 堆内存(Heap):所有线程公用的。 方法区(Method Area):有点像以前常说的 “进程代码段”,这里面存放了每个加载类的反射信息、类函数的代码、编译时常量等信息。 原生方法栈(Native Method Stack):主要用于 JNI 中的原生代码,平时很少涉及。而 Java 的使用的是堆内存, java 堆是一个运行时数据区,类的实例(对象)从中分配空间。Java 虚拟机(JVM) 的堆中储存着正在运行的应用程序所建立的所有对象,“ 垃圾回收 ”也是主要是和堆内存(Heap)有关。垃圾回收的概念就是 JAVA 虚拟机(JVM)回收那些不再被引用的对象内存的过程。一般我们认为正在被引用的对象状态为“alive”,而没有被应用或者取不到引用属性的对象状态为“dead” 。垃圾回收是一个释放处于”dead” 状态的对象的内存的过程。而垃圾回收的规则和算法被动态的作用于应用运行当中,自动回收。JVM 的垃圾回收器采用的是一种分代(generational )回收策略,用较高的频率对年轻的对象 (young generation)进行扫描和回收,这种叫做 minor collection,而对老对象(old generation) 的检查回收频率要低很多,称为 major collection。这样就不需要每次 GC 都将内存中所有对象都检查一遍,这种策略有利于实时观察和回收。(Sun JVM 1.3 有两种最基本的内存收集方式:一种称为 copying 或 scavenge,将所有仍然生存的对象搬到另外一块内存后,整块内存就可回收。这种方法有效率,但需要有一定的空闲内存,拷贝也有开销。这种方法用于 minor collection。另外一种称为 mark-compact,将活着的对象标记出来,然后搬迁到一起连成大块的内存,其他内存就可以回收了。这种方法不需要占用额外的空间,但速度相对慢一些。这种方法用于 major collection. )一些对象被创建出来只是拥有短暂的生命周期,比如 iterators 和本地变量。另外一些对象被创建是拥有很长的生命周期,比如 高持久化对象等。垃圾回收器的分代策略是把内存区划分为几个代,然后为每个代分配一到多个内存区块。当其中一个代用完了分配给他的内存后,JVM 会在分配的内存区内执行一个局部的 GC(也可以叫 minor collection)操作,为了回收处于“dead”状态的对象所占用的内存。局部 GC 通常要不 Full GC 要快很多。 JVM 定义了两个代,年轻代(yong generation)(有时称为“nursery”托儿所)和老年代(old generation)。年轻代包括 “Eden space(伊甸园)”和两个“survivor spaces”。虚拟内存初始化的时候会把所有对象都分配到 Eden space,并且大部分对象也会在该区域被释放。 当进行 minor GC 的时候,VM 会把剩下的没有释放的对象从 Eden space 移动到其中一个 survivor spaces 当中。此外, VM 也会把那些长期存活在 survivor spaces 里的对象移动到 老生代的“tenured” space 中。当 tenured generation 被填满后,就会产生 Full GC,Full GC 会相对比较慢因为回收的内容包括了所有的 live 状态的对象。pemanet generation 这个代包括了所有 java虚拟机自身使用的相对比较稳定的数据对象,比如类和对象方法等。关于代的划分,可以从下图中获得一个概况:如果垃圾回收器影响了系统的性能,或者成为系统的瓶颈,你可以通过自定义各个代的大小来优化它的性能。使用 JConsole,可以方便的查看到当前应用所配置的垃圾回收器的各个参数。想要获得更详细的参数,可以参考以下调优介绍:Tuning Garbage collection with the 5.0 HotSpot VMhttp:/java.sun.com/docs/hotspot/gc/index.html最后,总结一下各区内存:Eden Space (heap): 内存最初从这个线程池分配给大部分对象。Survivor Space (heap):用于保存在 eden space 内存池中经过垃圾回收后没有被回收的对象。Tenured Generation (heap):用于保持已经在 survivor space 内存池中存在了一段时间的对象。Permanent Generation (non-heap): 保存虚拟机自己的静态(refective)数据,例如类(class)和方法(method)对象。Java 虚拟机共享这些类数据。这个区域被分割为只读的和只写的,Code Cache (non-heap):HotSpot Java 虚拟机包括一个用于编译和保存本地代码(native code)的内存,叫做“代码缓存区” (code cache)第三部分 监控(工具发现问题)谈到内存监控工具,JConsole 是必须要介绍的,它是一个用 JAVA 写的 GUI 程序,用来监控 VM,并可监控远程的 VM,易用且功能强大。具体可监控 JAVA内存、JAVA CPU 使用率、线程执行情况、加载类概况等,Jconsole 需要在 JVM 参数中配置端口才能使用。由于是 GUI 程序,界面可视化,这里就不做详细介绍,具体帮助支持文档请参阅性能测试 JConsole 使用方法总结:http:/www.taobao.ali.com/chanpin/km/test/DocLib/性能测试辅助工具JConsole 的使用方法.aspx或者参考 SUN 官网的技术文档:http:/Java.sun.com/j2se/1.5.0/docs/guide/management/jconsole.htmlhttp:/Java.sun.com/javase/6/docs/technotes/tools/share/jconsole.html 在实际测试某一个项目时,内存出现泄露现象。起初在性能测试的 1 个小时中,并不明显,而在稳定性测试的时候才发现,应用的 HSF 调用在经过几个小时运行后,就出现性能明显下降的情况。在服务日志中报大量 HSF 超时,但所调用系统没有任何超时日志,并且压力应用的 load 都很低。经过查看日志后,认为应用可能存在内存泄漏。通过 jconsole 以及 jmap 工具进行分析发现,确实存在内存泄漏问题,其中 PS Old Gen 最终达到占用 100%的占用。如图所示:从上图可以看到,虽然每次 Full GC,JVM 内存会有部分回收,但回收并不彻底,不可回收的内存对象会越来越多,这样便会出现以上的一个趋势。在 Full GC 无法回收的对象越来越多时,最终已使用内存达到系统分配的内存最大值,系统最后无内存可分配,最终 down 机。第四部分 分析经过开发和架构师对应用的分析,查看此时内存队列,看哪个对象占用数据最多,再利用 jmap 命令,对线程数据分析,如下所示:num #instances #bytes class name-1: 9248056 665860032 com.taobao.matrix.mc.domain.*2: 9248031 295936992 com.taobao.matrix.*3: 9248068 147969088 java.util.*4: 1542111 37010664 java.util.Date前三个 instances 不断增加,指代的是同一个代码逻辑,异步分发的问题,堵塞消息,回收多次都无法回收成功。导致内存溢出。此外,对应用的性能单独做了压测,他的性能只能支撑到一半左右,故发送消息的 TPS,应用肯定无法处理过来,导致消息堆积,而 JAVA 垃圾回收期认为这些都是有用的对象,导致内存堆积,直至系统崩溃。调优方法由于具体调优方法涉及到应用的配置信息,故在此暂不列出,可以参考性能测试小组发布的性能测试调优宝典第四部分 总结内存溢出主要是由于代码编写时对某些方法、类应用不合理,或者没有预估到临时对象会占用很大内存量,或者把过多的数据放入 JVM 缓存,或者性能压力大导致消息堆积而占用内存,以至于在性能测试时,生成庞大数量的临时对象,GC 时没有做出有效回收甚至根本就不能回收,造成内存空间不足,内存溢出。如果编码之前,对内存使用量进行预估,对放在内存中的数据进行评估,保证有用的信息尽快释放,无用的信息能够被 GC 回收,这样在一定程度上是可以避免内存溢出问题的。
收藏 下载该资源
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号