性能测试总结之内存泄露和内存溢出

article/2025/9/15 5:14:31

刚刚做完了一个项目的性能测试,“有幸”也遇到了内存泄露的案例,所以在此和大家分享一下。

主要从以下几部分来说明,关于内存和内存泄露、溢出的概念,区分内存泄露和内存溢出;内存的区域划分,了解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虚拟机自身使用的相对比较稳定的数据对象,比如类和对象方法等。

关于代的划分,可以从下图中获得一个概况:

JVM

如果垃圾回收器影响了系统的性能,或者成为系统的瓶颈,你可以通过自定义各个代的大小来优化它的性能。使用JConsole,可以方便的查看到当前应用所配置的垃圾回收器的各个参数。想要获得更详细的参数,可以参考以下调优介绍:

Tuning Garbage collection with the 5.0 HotSpot VM

http://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.html

http://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回收,这样在一定程度上是可以避免内存溢出问题的。


http://chatgpt.dhexx.cn/article/N2WyLCWl.shtml

相关文章

内存泄露与内存溢出的区别及解决方法

内存溢出与泄露的区别 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。 内存泄露 memory leak&…

jvm故障 内存泄露和内存溢出总结

目录 内存泄漏memory leak 内存泄漏的分类(按发生方式来分类) 内存泄露的场景 静态集合类 / 长生命周期的对象持有短生命周期对象的引用 / 单例模式 /类加载器 各种连接,如数据库连接、网络连接和IO连接等 变量不合理的作用域 内部类持…

JVM——内存泄漏与内存溢出

内存泄漏与内存溢出 1. 面试题 什么是内存泄漏和什么是内存溢出 (陌陌) Java存在内存泄漏吗,内存泄漏的场景有哪些,如何避免(百度) Java 中会存在内存泄漏吗,简述一下?(猎聘) 内存泄漏是怎么造成的?(拼多多、字节跳动)…

Java中内存溢出和内存泄露详解

1、内存溢出(OOM) 在程序中导致程序崩溃的两种原因有: ①、空指针、下标越界等异常,这类问题主要原因是因为代码写的有问题 ②、还有一类问题是,代码看着也没有问题,在进行GC时,回收也没有空出足…

内存泄漏和内存溢出的区别

内存泄漏就是在jvm堆中生成的对象,经过垃圾回收器回收后也无法得到有效的释放,则会导致可用的虚拟机可分配的内存越来越少。最终可能导致内存溢出。 以发生的方式来分类,内存泄漏可以分为4类: 1、常发性内存泄漏。发生内存泄漏的…

什么是内存泄漏和内存溢出

目录 一、内存溢出 (OutOfMemory)二、内存泄漏 (Memory Leak) 一、内存溢出 (OutOfMemory) 它是指程序在申请内存时,没有足够的内存空间供其使用,抛出OutOfMemory异常。 比如申请了一个8MB空间,但是当前内存可用空间只有5MB,那么…

内存溢出和内存泄漏

内存溢出: 一、内存溢出相对于内存泄漏来说,尽管更容易被理解,但是同样的,内存溢出也是引发程序崩溃的罪魁祸首之一。 二、由于GC一直在发展,所有一般情况下,除非应用程序占用的内存增长速度非常快&#…

Vim替换命令substitute介绍

原文地址:再谈Vim substitute替换命令-Vim入门教程(54) 在Vim替换命令一文介绍过,substitute 命令的语法格式为::[range]s[ubstitute]/{pattern}/{string}/[flags]。 [flags] 表示可选的标志位,常用的包括 g、c、n、e 等。其中&…

VIM替换命令%s

[rootlocalhost 09:03:42 /home/test]# cat vim_test.txt linux nginx aabb bbcc ccdd vim vim_test.txt 进入编辑模式 输入指令 :%s/bb/zz/gc 全局替换,带确认 按y对搜索项替换第一个 按y对搜索项替换第二个 这种操作方法 ,查看替换过程与效果。避…

八皇后问题c语言算法

目录 [TOC] 问题分析: 相信八皇后规则的问题,大家都很熟悉,接下来是如何分析回溯法的应用。回溯法与图里面的深度优先遍历非常的类似,就是,在满足题目条件时候,它总是优先选择第一个,当不满足…

八皇后算法

两个皇后都不能处于同一行、同一列或同一斜线上 讨论如果是八个皇后,即存在于8*8的行列式中,每行每列一定存在一个皇后,现在我们用queen[row]col 表示在第row行的皇后处于第col列, 位置的选定,5人 然后判断此时皇后所…

【算法】八皇后问题

1. 问题描述 在 8 * 8 的国际象棋棋盘上摆放八个皇后,任意两个皇后不能处于同一行、同一列或同一斜 线上,请求出所有的摆法。 2. 算法描述 用回溯算法来考虑此问题,在放置皇后之前判断该位置是否会产生冲突。有冲突就继续判断 下一个位置&…

八皇后问题算法解析

八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。 该问题是国际西洋棋棋手马克斯贝瑟尔于1848年提出:在88格的国际象棋上摆放八个皇后,使其不能互相攻击, 即任意两个皇后都不能处于同一行、同一列或同一斜线…

用遗传算法解决八皇后问题

一、问题描述 八皇后问题的目标是在国际象棋棋盘中放置8个皇后,使得任何一个皇后都不会攻击到其他任一皇后。(皇后可以攻击和它在同一行,同一列或者同一对角线的任何棋子) 二、编程语言及算法 编程语言:python 算法:遗传算法 …

经典算法问题-01-八皇后

#八皇后问题 ###问题描述: 八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。在88格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种…

【算法】八皇后问题解法(c++版)

八皇后问题是回溯算法里面的经典问题,起源于1848年由国际西洋棋手马克斯,贝瑟尔提出,1850年法国著名的数学家高斯提出共有76种解法,实际上真的是这样吗,多年后我们通过计算机程序可以发现真正的解法比76种更多。 问题描…

递归算法——八皇后问题 python

研究了一下午的八皇后算法,可算是搞明白了,为了避免以后忘记,还是写个博客吧!可能会跟其他文章有相似之处,最终还是希望能好好学习算法,都是经过自己思考后亲自写的代码,虽然过程比较艰难&#…

算法课设——八皇后问题

八皇后问题,是由国际象棋棋手马克斯贝瑟尔于1848年提出的问题,是回溯算法的典型案例。 问题表述为:在88格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,…

八皇后问题(递归算法)

作者:非妃是公主 专栏:《笔记》《C》 个性签:顺境不惰,逆境不馁,以心制境,万事可成。——曾国藩 文章目录 想必八皇后问题,学过C的老哥都已经有所了解了: 题目是:在一个…

C语言:八皇后问题----回溯算法

【问题引入】 在88格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图…