频繁分配释放内存导致的性能问题分析
- 格式:docx
- 大小:149.09 KB
- 文档页数:5
性能测试常见问题分析⼀、内存溢出1、堆内存溢出现象: (1)压测执⾏⼀段时间后,系统处理能⼒下降。
这时⽤JConsole、JVisualVM等⼯具连上服务器查看GC情况,每次GC回收都不彻底并且可⽤堆内存越来越少。
(2)压测持续下去,最终在⽇志中有报错信息:ng.OutOfMemoryError.Java heap space。
排查⼿段: (1)使⽤jmap -histo pid > test.txt命令将堆内存使⽤情况保存到test.txt⽂件中,打开⽂件查看排在前50的类中有没有熟悉的或者是公司标注的类名,如果有则⾼度怀疑内存泄漏是这个类导致的。
(2)如果没有,则使⽤命令:jmap -dump:live,format=b,file=test.dump pid⽣成test.dump⽂件,然后使⽤MAT进⾏分析。
(3)如果怀疑是内存泄漏,也可以使⽤JProfiler连上服务器在开始跑压测,运⾏⼀段时间后点击“Mark Current Values”,后续的运⾏就会显⽰增量,这时执⾏⼀下GC,观察哪个类没有彻底回收,基本就可以判断是这个类导致的内存泄漏。
解决⽅式:优化代码,对象使⽤完毕,需要置成null。
2、永久代 / ⽅法区溢出现象:压测执⾏⼀段时间后,⽇志中有报错信息:ng.OutOfMemoryError: PermGen space。
产⽣原因:由于类、⽅法描述、字段描述、常量池、访问修饰符等⼀些静态变量太多,将持久代占满导致持久代溢出。
解决⽅法:修改JVM参数,将XX:MaxPermSize参数调⼤。
尽量减少静态变量。
3、栈内存溢出现象:压测执⾏⼀段时间后,⽇志中有报错信息:ng.StackOverflowError。
产⽣原因:线程请求的栈深度⼤于虚拟机所允许的最⼤深度,递归没返回,戒者循环调⽤造成。
解决⽅法:修改JVM参数,将Xss参数改⼤,增加栈内存。
栈内存溢出⼀定是做批量操作引起的,减少批处理数据量。
内存溢出的三种情况及系统配置解决方案内存溢出是指程序在运行过程中申请的内存超过了系统或者进程所能提供的上限。
导致内存溢出的原因可能是程序中存在内存泄漏、内存分配过多或者递归调用过深等。
下面将介绍三种常见的内存溢出情况及其系统配置解决方案。
1.程序内存泄漏导致内存溢出:内存泄漏指程序在运行过程中动态分配内存空间后,没有对其进行释放,导致一部分内存无法再次使用。
长时间运行的程序中,如果内存泄漏较为严重,系统可用内存会不断减少,直到最终耗尽所有内存资源。
解决方案:使用内存泄漏检测工具来检测和修复程序中的内存泄漏问题。
同时,可以考虑使用自动内存管理的编程语言,如Java和Python,在程序运行过程中自动回收未使用的内存。
2.内存分配过多导致内存溢出:解决方案:优化程序的内存使用,尽可能减小内存分配的数量和大小。
可以通过使用更高效的内存管理算法来减少内存碎片,或者使用内存池技术来提前分配一定量的内存供程序使用。
3.递归调用过深导致内存溢出:递归函数在每次调用时会将一定量的数据压入栈中,如果递归调用层数过深,栈的空间可能会超过系统的限制,从而导致内存溢出。
这种情况通常发生在没有设置递归终止条件或者递归层数过多的情况下。
解决方案:优化递归算法,设置合适的递归终止条件,避免递归调用过深。
如果无法避免使用递归算法,可以考虑使用尾递归或者迭代算法来替代递归调用,减少栈的压力。
在系统配置方面,可以采取以下措施来预防和解决内存溢出问题:1.增加系统内存容量:如果内存溢出是由于系统可用内存不足引起的,可以考虑增加系统的内存容量。
这可以通过增加物理内存条或者使用虚拟内存技术来实现。
虚拟内存技术会将部分磁盘空间用作缓存,并将一部分数据暂时存储在磁盘上,以释放内存空间。
2. 调整JVM参数:对于使用Java虚拟机(JVM)的应用程序,可以通过调整JVM的参数来控制内存的分配和管理。
例如,可以通过设置-Xmx参数来限制JVM使用的最大堆内存大小,或者通过设置-XX:MaxPermSize参数来限制JVM使用的最大持久代(PermGen)内存大小。
内存故障对电脑的影响分析1. 引言内存是计算机系统中一个非常重要的组成部分,它负责存储和提供数据给中央处理器(CPU)进行计算和运行程序。
然而,由于各种原因,内存故障可能会发生,严重影响计算机的性能和稳定性。
因此,本文将分析内存故障对电脑的影响,并讨论相关解决方案。
2. 内存故障的种类内存故障可以分为硬件故障和软件故障两种类型。
2.1 硬件故障硬件故障是指内存模块本身出现物理损坏或其他硬件问题,导致内存无法正常工作。
常见的硬件故障包括内存芯片损坏、内存插槽松动、电压不稳等问题。
2.2 软件故障软件故障是指由于操作系统或应用程序错误引起的内存问题。
例如,内存泄漏是指应用程序在运行过程中未能正确释放已分配的内存,导致内存持续增加,最终耗尽所有可用内存。
3. 内存故障对电脑的影响内存故障对电脑的影响主要表现在以下几个方面:3.1 性能下降当内存出现故障时,计算机的运行速度会显著下降。
这是因为内存故障可能导致数据读取和写入的错误,使得CPU无法正常获取所需的数据或将数据存储到内存中。
3.2 稳定性问题内存故障也会引起计算机的稳定性问题。
在内存出现问题的情况下,计算机可能会频繁崩溃,出现蓝屏或死机等现象。
这些问题对于用户的工作和使用体验来说将是一个极大的困扰。
3.3 数据丢失当内存模块出现故障时,数据可能会丢失或损坏。
这是由于内存故障可能导致数据写入错误,使得存储在内存中的数据无法正确保存。
对于用户而言,数据丢失可能导致重要文件或者工作进度的损失。
4. 解决方案针对内存故障对电脑的影响,以下是一些解决方案:4.1 硬件故障的解决方案•检查内存插槽是否松动,确保内存模块正确安装。
•清洁内存模块和插槽,确保没有灰尘或腐蚀物。
•使用内存测试工具诊断和修复内存问题。
•如有需要,更换损坏的内存模块。
4.2 软件故障的解决方案•更新操作系统和应用程序以修复已知的内存问题。
•使用内存管理工具检测和修复内存泄漏问题。
•重新安装软件以解决可能的软件故障。
解决内存泄漏和性能问题的方法内存泄漏和性能问题是软件开发中常见的挑战。
当程序中的内存被错误地分配或没有被正确释放时,就会出现内存泄漏。
这会导致系统性能下降并可能导致系统崩溃。
解决内存泄漏和性能问题需要细致地分析和调优代码。
下面是一些解决内存泄漏和性能问题的常用方法。
1.使用合适的数据结构:选择合适的数据结构对系统性能至关重要。
例如,使用数组代替链表在访问元素时会更快。
此外,选择合适的哈希函数和哈希表大小对于哈希表的性能也非常重要。
2.合理使用内存:正确分配和释放内存是避免内存泄漏的关键。
确保在使用完变量后及时释放内存,并避免内存泄漏。
使用垃圾回收机制或自动内存管理工具可以帮助减少内存泄漏的发生。
3.减少资源使用:资源是有限的,过多地使用资源会导致性能下降。
因此,应该减少资源的使用,比如合理设计数据库模型、优化网络通信等。
4.避免频繁的内存分配和释放:频繁的内存分配和释放会降低系统性能。
为了避免频繁的内存分配和释放,可以使用对象池或缓存机制。
对象池是一种重复使用对象的方法,可以减少内存分配的次数。
5.使用合适的算法和数据结构:选择合适的算法和数据结构可以提高程序的性能。
例如,使用快速排序而不是冒泡排序可以大大提高排序的效率。
了解各种算法和数据结构的特点并选择适当的方法非常重要。
6.优化循环和递归:循环和递归是程序中最常见的结构。
优化循环和递归可以提高程序的性能。
通过减少循环或递归的迭代次数、优化循环体内部的代码等方式可以提高性能。
7.剖析和调优代码:使用剖析工具(如性能剖析器)来确定代码的性能瓶颈。
通过分析剖析结果,可以找到需要调优的地方。
在调优代码时,要尽量避免过早优化,应该根据剖析结果有针对性地进行调优。
8.合理使用缓存:缓存是提高系统性能的重要手段之一。
合理使用缓存可以减少对数据库和其他资源的访问次数,提高系统响应速度。
9.并发和并行处理:合理地使用并发和并行处理可以提高系统的性能。
通过将任务分为多个子任务并行处理,可以提高系统的响应速度。
如何进行性能优化减少代码的执行时间一、概述性能优化是软件开发中非常重要的一环,它旨在提高程序的执行效率和响应速度。
在本文中,将介绍一些常见的性能优化技巧,以减少代码的执行时间。
二、算法优化1. 选择合适的算法:不同的算法对于同一问题的解决方式可能存在差异,选择合适的算法可以大大减少代码执行的时间。
2. 减少循环次数:在循环中进行频繁的计算可能会导致性能下降,可以通过合理设置循环条件或者减少循环次数来优化程序的性能。
3. 使用空间换时间的策略:可以使用缓存或者索引等数据结构来加速代码的执行,虽然会占用更多的内存空间,但是可以减少代码执行的时间。
三、代码结构优化1. 减少函数调用次数:函数调用会产生一定的开销,过多的函数调用会降低程序的性能,可以通过减少函数的调用次数来提高执行效率。
2. 减少代码重复:重复的代码会导致冗余的计算,可以将重复的代码封装为函数或者将其放在循环外部,以减少计算次数。
3. 优化条件判断:可以通过重新排列条件判断的顺序,将出现频率较高的条件判断放在前面,减少无效的判断,提高代码执行效率。
四、数据结构优化1. 使用合适的数据结构:选择合适的数据结构可以提高代码的执行效率,比如使用哈希表可以快速查找数据,使用数组可以提高数据的访问速度等。
2. 优化内存分配:频繁的内存分配和释放会导致性能下降,可以预先分配一块足够的内存空间,避免频繁的内存分配操作。
3. 使用缓存:将频繁访问的数据存放在缓存中,可以提高数据的读取速度,减少代码的执行时间。
五、并发优化1. 多线程或者多进程:使用多线程或者多进程可以将任务分解成多个子任务,并行执行,提高程序的执行效率。
2. 锁优化:对于共享资源的访问,使用合适的锁机制可以避免多个线程同时访问,减少资源竞争,提高执行效率。
六、工具优化1. 使用性能分析工具:通过使用性能分析工具可以找出代码的瓶颈所在,优化性能。
2. 编译优化:使用合适的编译选项可以进行代码优化,提高代码的执行效率。
VSCode中的代码性能分析与优化在软件开发过程中,代码性能的优化是至关重要的。
VSCode作为一款功能强大的代码编辑器,提供了许多工具和插件,可以帮助开发者分析和优化代码性能。
本文将介绍如何在VSCode中进行代码性能分析与优化。
一、代码性能分析工具代码性能分析是找出代码中的性能问题和瓶颈的过程。
VSCode提供了多种代码性能分析工具,以下是其中一些常用的工具:1. Profiler:Profiler是一种性能分析器,可以帮助开发者找出代码中的性能瓶颈。
通过在代码中插入一些监测点,Profiler可以记录代码的执行时间和资源使用情况,并生成性能分析报告。
在VSCode中,可以通过安装插件来集成Profiler工具。
2. Debug工具:VSCode内置了强大的调试功能,可以帮助开发者逐行执行代码并查看变量的值和执行流程。
通过使用debug工具,开发者可以找到代码中的潜在性能问题,并进行优化。
3. 插件扩展:VSCode拥有丰富的插件市场,很多插件可以帮助开发者进行代码性能分析。
例如,"CodeMetrics"插件可以统计代码行数和圈复杂度等指标,帮助开发者评估代码的性能。
二、代码性能优化方法在进行代码性能优化之前,首先需要进行性能分析,找出代码中的瓶颈。
基于性能分析的结果,可以采取以下几种优化方法:1. 编写高效的算法和数据结构:选择合适的算法和数据结构可以减少代码的执行时间和内存消耗。
例如,使用哈希表(Hash Table)来替代线性查找可以显著提高代码的执行速度。
2. 减少系统调用和IO操作:系统调用和IO操作通常是代码性能的瓶颈之一。
在代码中尽量减少不必要的文件读写和网络请求,可以提高代码的执行效率。
3. 避免过多的内存分配和释放:频繁的内存分配和释放会导致额外的开销。
可以使用对象池、缓存等技术来减少内存分配和释放的次数,提高代码的性能。
4. 多线程和并发处理:对于需要处理大量并行任务的代码,可以考虑使用多线程或并发处理来提高代码的执行效率。
如何减少内存泄漏提高程序的稳定性与性能在软件开发过程中,内存泄漏是一个常见而严重的问题。
内存泄漏指的是由于程序在分配内存后未正确释放,导致内存空间无法再被其他程序使用,从而导致程序性能下降和稳定性问题。
本文将从四个方面讨论如何减少内存泄漏,以提高程序的稳定性与性能。
##1. 理解内存泄漏的原因和影响首先,我们需要理解内存泄漏的原因和影响。
内存泄漏通常发生在程序执行过程中,当程序分配内存后,却没有释放或者不正确释放,导致内存空间无法被回收再利用。
这导致程序占用的内存越来越多,最终耗尽系统资源,使得程序崩溃或者运行缓慢。
内存泄漏不仅会影响程序的性能,还会给系统带来严重的稳定性问题。
当大量内存被占用时,系统的响应速度下降,甚至可能导致系统崩溃。
因此,减少内存泄漏是维护程序性能和稳定性的关键。
##2. 使用合适的数据结构和算法选择合适的数据结构和算法是减少内存泄漏的基础。
一些常见的数据结构如链表、栈、队列等,在使用过程中需要特别注意内存的分配和释放。
如果使用不当,容易导致内存泄漏。
因此,开发者在设计和实现程序时,应仔细选择合适的数据结构和算法,确保内存的正确分配和释放。
另外,尽量避免使用过于复杂的数据结构和算法,因为这可能会导致额外的内存占用和运算开销。
考虑到程序的稳定性和性能,选择简洁高效的数据结构和算法是非常重要的。
##3. 注意内存的分配和释放内存的分配和释放是减少内存泄漏的关键。
在程序执行过程中,开发者应该注意内存的及时释放,避免出现不必要的内存占用。
首先,合理地规划内存的分配。
在程序设计和实现过程中,尽量避免过多的内存动态分配。
如果频繁分配内存,容易引发内存泄漏的问题。
相反,通过合理的内存规划和静态分配,可以减少内存占用和内存泄漏的风险。
其次,正确地释放内存是非常重要的。
当某个内存块不再使用时,开发者应该主动释放该内存,以便其他程序可以重新利用。
大多数编程语言都提供了相应的内存管理机制,如C语言中的malloc和free,Java语言中的垃圾回收机制等。
使用free的注意事项使用free的注意事项当我们使用free时,需要注意以下几点:1. 避免滥用:虽然free是一个非常有用的工具,可以帮助我们管理内存,但是滥用它可能会导致内存泄漏或者程序崩溃的情况发生。
因此,在使用free之前,我们必须确保我们已经动态地分配了内存,而不是去释放一个静态分配的变量。
2. 不要重复释放内存:当我们使用free释放一块内存后,该内存区域就变得无效了。
如果我们尝试重新释放已经释放的内存,就可能导致程序崩溃。
因此,在使用free之前,应该确保我们只释放了被动态分配的内存。
3. 释放指针所指向的内存:当我们使用指针来访问动态分配的内存时,我们需要使用free来释放指针所指向的内存,而不是释放指针本身。
释放指针本身是错误的操作,会导致程序出现未定义的行为。
4. 避免野指针:在程序中,我们应该避免使用已经释放的内存。
如果在使用free之后,我们继续使用已经释放的指针,就会产生野指针。
野指针是非常危险的,会引发难以追踪的错误。
因此,在使用free之后,应该将指针设置为NULL,以避免出现野指针的情况。
5. 建立好的内存分配与释放的习惯:在编写程序时,我们应该建立好的内存分配与释放的习惯。
这包括始终在使用malloc、calloc或realloc分配内存之后,使用free来释放内存。
同时,我们也应该避免使用过多的动态内存分配,以减小内存管理的压力。
6. 处理malloc失败的情况:当动态分配内存的时候,可能会出现malloc失败的情况,这是因为系统没有足够的内存来分配所需的内存空间。
在这种情况下,malloc将返回一个NULL指针。
因此,在调用malloc之后,我们应该检查返回的指针是否为NULL,以确保内存分配成功。
7. 注意内存泄漏:内存泄漏是指在程序运行过程中,分配的内存没有被正确释放,造成内存资源的浪费。
为了避免内存泄漏,我们应该在程序的适当位置使用free来释放动态分配的内存。
heap profiler使用Heap Profiler(堆分析器)是一种用于分析和优化程序内存使用的工具。
它可以帮助开发人员找到内存泄漏和内存使用不当的问题,并提供解决方案。
本文将介绍Heap Profiler的基本原理和使用方法。
一、Heap Profiler的基本原理Heap Profiler通过监视程序运行时的堆内存分配和释放情况,来收集内存使用的统计信息。
它会记录每个内存块的大小、地址和分配/释放操作的时间戳。
借助这些信息,Heap Profiler可以生成详细的内存分配情况报告,帮助开发人员发现内存泄漏、内存使用不当和内存碎片化等问题。
二、Heap Profiler的使用方法1. 引入Heap Profiler库:首先,在需要分析的程序中引入Heap Profiler的库文件。
根据不同的编程语言和开发环境,可能需要进行相应的配置和设置。
2. 启动Heap Profiler:在程序运行前或运行过程中,通过调用Heap Profiler的API来启动它。
启动Heap Profiler后,它将开始监视程序的堆内存分配和释放情况。
3. 运行程序:运行程序,让Heap Profiler记录内存分配和释放的操作。
可以模拟真实场景下的程序运行,以获得更准确的分析结果。
4. 生成报告:当程序运行结束或达到一定条件时,可以通过Heap Profiler提供的API来生成内存分配报告。
报告中包含了各个内存块的大小、地址、分配/释放操作的时间戳等信息。
5. 分析报告:根据生成的报告,开发人员可以分析内存分配情况,找出内存泄漏和内存使用不当的问题。
报告中通常会标注出内存泄漏的位置和造成内存泄漏的原因,方便开发人员进行调试和修复。
6. 优化程序:根据分析结果,开发人员可以对程序进行优化,减少内存泄漏和内存使用不当的情况。
比如,释放不再使用的内存块、使用合适的数据结构等。
三、Heap Profiler的优势和适用场景1. 发现内存泄漏:Heap Profiler可以帮助开发人员发现程序中的内存泄漏问题,指出造成内存泄漏的原因和位置。
如何进行程序性能优化和调优程序性能优化和调优是一项重要的任务,可以显著提高程序的运行效率和响应速度。
本文将介绍一些常用的方法和技巧,帮助您进行程序性能优化和调优。
一、分析程序性能瓶颈在进行程序性能优化和调优时,首先需要分析程序的性能瓶颈。
通过定位性能瓶颈,我们可以有针对性地进行优化。
1. 使用性能分析工具使用性能分析工具,如profiler,可以帮助您找到程序运行过程中的性能瓶颈。
这些工具会记录程序的运行状态,生成性能报告,分析程序的热点代码和耗时操作。
2. 逐行检查代码仔细检查程序中的每一行代码,找出可能导致性能问题的地方。
特别关注循环、递归、多次调用的代码段等。
二、优化算法和数据结构优化算法和数据结构是提升程序性能的关键。
通过选择适当的算法和优化数据结构,可以减少程序的运行时间和内存占用。
1. 使用高效的算法选择最适合具体问题的算法,并注意评估算法的时间复杂度和空间复杂度。
避免使用低效的算法,尽可能采用更高效的替代方案。
2. 优化数据结构合理选择数据结构,减少内存占用和操作时间。
例如,使用哈希表代替线性搜索,使用二叉搜索树代替线性表等。
三、并发和并行优化合理利用并发和并行计算,可以进一步提高程序的性能。
1. 多线程优化将程序拆分为多个线程,充分利用多核CPU的优势。
但需要注意避免线程竞争和死锁等问题。
2. 并发数据结构使用并发数据结构,如并发队列、并发哈希表等,来实现并发访问和更新。
避免数据争用和线程阻塞。
四、内存管理和优化合理管理程序的内存分配和使用,可以减少内存泄漏和提高程序的运行效率。
1. 减少内存分配和释放次数避免频繁申请和释放内存,可以减少内存分配器的开销。
可通过对象池、内存池等技术实现。
2. 内存复用和缓存重复利用已分配的内存,避免重复创建和销毁对象。
通过缓存常用数据,减少对内存的频繁读写。
五、代码优化技巧采用一些代码级的优化技巧,可以进一步提高程序性能。
1. 减少函数调用函数调用会增加额外的开销。
内核态与用户态是操作系统的两种运行级别,intel cpu提供Ring0-Ring3三种级别的运行模式。
Ring0级别最高,Ring3最低。
当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。
此时处理器处于特权级最高的(0级) 内核代码中执行。
当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。
每个进程都有自己的内核栈。
当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。
即此时处理器在特权级最低的(3级)用户代码中运行。
在内核态下CPU可执行任何指令,在用户态下CPU只能执行非特权指令。
当CPU处于内核态,可以随意进入用户态;而当CPU处于用户态时,用户从用户态切换到内核态只有在系统调用和中断两种情况下发生,一般程序一开始都是运行于用户态,当程序需要使用系统资源时,就必须通过调用软中断进入内核态。
现象
1 压力测试过程中,发现被测对象性能不够理想,具体表现为:
进程的系统态CPU消耗20,用户态CPU消耗10,系统idle大约70
2 用ps -o majflt,minflt -C program命令查看,发现majflt每秒增量为0,而minflt每秒增量大于10000。
初步分析
majflt代表major fault,中文名叫大错误,minflt代表minor fault,中文名叫小错误。
这两个数值表示一个进程自启动以来所发生的缺页中断的次数。
当一个进程发生缺页中断的时候,进程会陷入内核态,执行以下操作:
检查要访问的虚拟地址是否合法
查找/分配一个物理页
填充物理页内容(读取磁盘,或者直接置0,或者啥也不干)
建立映射关系(虚拟地址到物理地址)
重新执行发生缺页中断的那条指令
如果第3步,需要读取磁盘,那么这次缺页中断就是majflt,否则就是minflt。
此进程minflt如此之高,一秒10000多次,不得不怀疑它跟进程内核态cpu消耗大有很大关系。
分析代码
查看代码,发现是这么写的:一个请求来,用malloc分配2M内存,请求结束后free这块内存。
看日志,发现分配内存语句耗时10us,平均一条请求处理耗时1000us 。
原因已找到!
虽然分配内存语句的耗时在一条处理请求中耗时比重不大,但是这条语句严重影响了性能。
要解释清楚原因,需要先了解一下内存分配的原理。
内存分配的原理
从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap (不考虑共享内存)。
brk是将数据段(.data)的最高地址指针_edata往高地址推,mmap是在进程的虚拟地址空间中(一般是堆和栈中间)找一块空闲的。
这两种方式分配的都是虚拟内存,没有分配物理内存。
在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。
在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层是由brk,mmap,munmap这些系统调用实现的。
下面以一个例子来说明内存分配的原理:
1进程启动的时候,其(虚拟)内存空间的初始布局如图1所示。
其中,mmap内存映射文件是在堆和栈的中间(例如libc-2.2.93.so,其它数据文件等),为了简单起见,省略了内存映射文件。
_edata指针(glibc里面定义)指向数据段的最高地址。
2进程调用A=malloc(30K)以后,内存空间如图2:malloc函数会调用brk系统调用,将_edata 指针往高地址推30K,就完成虚拟内存分配。
你可能会问:只要把_edata+30K就完成内存分配了?事实是这样的,_edata+30K只是完成虚拟地址的分配,A这块内存现在还是没有物理页与之对应的,等到进程第一次读写A这块内存的时候,发生缺页中断,这个时候,内核才分配A这块内存对应的物理页。
也就是说,如果用malloc分配了A这块内容,然后从来不访问它,那么,A对应的物理页是不会被分配的。
3进程调用B=malloc(40K)以后,内存空间如图3.
4进程调用C=malloc(200K)以后,内存空间如图4:默认情况下,malloc函数分配内存,如果请求内存大于128K(可由M_MMAP_THRESHOLD选项调节),那就不是去推_edata指针了,而是利用mmap系统调用,从堆和栈的中间分配一块虚拟内存。
这样子做主要是因为brk分配的内存需要等到高地址内存释放以后才能释放(例如,在B释放之前,A是不可能释放的),而mmap分配的内存可以单独释放。
当然,还有其它的好处,也有坏处,再具体下去,有兴趣的同学可以去看glibc里面malloc的代码了。
5进程调用D=malloc(100K)以后,内存空间如图5.
6进程调用free(C)以后,C对应的虚拟内存和物理内存一起释放
7进程调用free(B)以后,如图7所示。
B对应的虚拟内存和物理内存都没有释放,因为只有一个_edata指针,如果往回推,那么D这块内存怎么办呢?当然,B这块内存,是可以重用的,如果这个时候再来一个40K的请求,那么malloc很可能就把B这块内存返回回去了。
8进程调用free(D)以后,如图8所示。
B和D连接起来,变成一块140K的空闲内存。
9默认情况下:当最高地址空间的空闲内存超过128K(可由M_TRIM_THRESHOLD选项调节)时,执行内存紧缩操作(trim)。
在上一个步骤free的时候,发现最高地址空闲内存超过128K,于是内存紧缩,变成图9所示。
真相大白
说完内存分配的原理,那么被测模块在内核态cpu消耗高的原因就很清楚了:每次请求来都malloc一块2M的内存,默认情况下,malloc调用mmap分配内存,请求结束的时候,调用munmap释放内存。
假设每个请求需要5个物理页,那么每个请求就会产生5个缺页中断,在2000的压力下,每秒就产生了10000多次缺页中断,这些缺页中断不需要读取磁盘解决,所以叫做minflt;缺页中断在内核态执行,因此进程的内核态cpu消耗很大。
缺页中断分散在整个请求的处理过程中,所以表现为分配语句耗时(10us)相对于整条请求的处理时间(1000us)比重很小。
解决办法
将动态内存改为静态分配,或者启动的时候,用malloc为每个线程分配,然后保存在threaddata里面。
但是,由于这个模块的特殊性,静态分配,或者启动时候分配都不可行。
另外,Linux下默认栈的大小限制是10M,如果在栈上分配几M的内存,有风险。
禁止malloc调用mmap分配内存,禁止内存紧缩。
在进程启动时候,加入以下两行代码:
mallopt(M_MMAP_MAX, 0); // 禁止malloc调用mmap分配内存
mallopt(M_TRIM_THRESHOLD, -1); // 禁止内存紧缩
效果:加入这两行代码以后,用ps命令观察,压力稳定以后,majlt和minflt都为0。
进程的系统态cpu从20降到10。
小结
可以用命令ps -o majflt minflt -C program来查看进程的majflt, minflt的值,这两个值都是累加值,从进程启动开始累加。
在对高性能要求的程序做压力测试的时候,我们可以多关注一下这两个值。
如果一个进程使用了mmap将很大的数据文件映射到进程的虚拟地址空间,我们需要重点关注majflt的值,因为相比minflt,majflt对于性能的损害是致命的,随机读一次磁盘的耗时数量级在几个毫秒,而minflt只有在大量的时候才会对性能产生影响。