理解内存管理:追踪、泄漏和优化
虽然这名字很长,但其实就是一码事,试问你做内存跟踪不是为了看泄露?试问你看到了泄露和碎片不回去优化?哈哈
理论知识咱不具备,所以现实点,从实践出发好了。
我一向干点啥事,都是被勾引的,先是要解决问题,然后又觉得不够过瘾,就变成优化问题本身。这回的任务原本是内存泄露,我东一榔头,西一锤子的,却是满世界乱敲,敲到最后,原来的问题早湮灭了,剩下的,只是自己的问题而已。
内存问题由来已久,最早做c++的时候,包装的很好,这个东西其实不明显,因为多半是new和delete,malloc和free显得特别扎眼,泾渭分明,最大的麻烦也不过是从这里new/malloc,去那里delete/free,这种东西极少,反而很好抓出来暴打五十大板。所以那会名义上boundschecker啥的用了一堆,实际上却没什么技术活,都拼体力去了,反正就几个点,我单步还不成么。
这回却是一个很好的锻炼机会,因为遇到了一个类似photoshop的庞然大物,而且代码从c到c++到c#,满世界的malloc,new,GC,这要泄露起来,可谓是五毒俱全,声色一时无两啊。哈哈哈哈(被吓傻了)
Problem1:内存跟踪
1:方法论
对于内存的跟踪,其实办法很多,我所知道的,可以归为几类
宏定义法:不管现代版的new还是古典版的malloc,#define吃遍天下无敌手啊,#define malloc mymalloc #define new mynew不就成了。
Hook法:其实new和malloc都提供有hook方法,《Windows核心编程》就提到了非著名的crtdbg库(对于初级程序员,这东西少有人知啊)。
重载法:对于C++来说,其实还有个重载全局new的方法可以用。。。。。
工具法:著名工具BoundsChecker,processXP等。。。。。。。。
GC法:GC值得单独拿出来说,因为你没法干涉他,但是又能找到很多工具,比如微软的CLR Profiler,比如收费的.net memory profiler,以及各种重量级。。。
2:世界观
最重要的问题,就是,你的问题是什么,所以我称之为世界观问题,比如,就内存泄露而言,对于纯C++程序而言(现代C++),new和delete以及设计良好的class可以极好的屏蔽内存泄露的问题,所以看到内存泄露,我认为合格的c++程序员应该首先考虑设计的合理性。而对于内存碎片问题,优雅高效的内存分配算法和一个设计合理的memory pool 才是终极兵器。
相对来说,核心问题,也就是最终决定你能不能解决问题的东西,是程序员的内存观念,或者说对内存的理解,这方面推荐《windows核心编程》。至少总得知道用户内存空间只有2G,知道virtualAlloc,知道HeapAlloc,知道dll对内存的共享,知道内存碎片化的后果,知道tls。。。。。。
话说回来,一个全局的内存跟踪器仍然是一大杀器,你说内存有问题,他说内存没问题,你说你优化了,他说效率没有变,你说我把内存全打出来了,吃了多少,费了多少,漏了多少,时间节省了多少,他。。。。。。。。。
最后我选了条通俗易懂的道道,#define malloc/free,当然,你还需要一个c级别的list,我想,就那么多了,虽然一大片废话只得到这么个结果。
3:实践
实践永远比动脑子臆想复杂,比如我臆想着一个define+list就可以开杀的时候,发现还不是那么回事。比如,当你需要跨越多层lib,在所有dll中统计全局内存的时候。事情就变复杂了,首先,你希望看到每个dll自己的内存,接着,你不想牺牲太多效率,然后,你还要结果能够易于访问,最后,别忘了多线程同步。我想破了脑袋,才整出一个临时解决方案的0.1beta版而已。
1:一个公用的memory库,定义一个static变量用来指向memory list,这样可以保证每个使用这个库的dll拥有各自的memorylist。
2:一个全局的FileMapping,保存所有dll中memorylist头指针。这样可以随时遍历全局内存,并且Filemapping是系统级别的可见,甚至可以在其他进程中访问。
3:简单有效的数据结构(包括memoryinfo和globalmemoryinfo),良好的数据同步机制。
Problem 2:内存泄露
看到完整的内存统计数据的时候,我乐得那是一个合不拢嘴啊,这个刀基本上是磨了个1/3出来了,简单砍砍柴,那是问题不大。
有了统计信息,我们就可以解决一部分问题了,只要再加上一个snapShot的功能,再加上compare一下,就可以针对问题,发现未释放内存的大小和地址了。对于跟踪大块内存泄露,马上就从蒙昧时代,进化到刀耕火种了。现在只需要一个条件断点,就可以把问题,跟个八九不离十出来。
但再好它也只是个柴刀,装备不够好啊。可是加上了CallStack,那可就是电锯和柴刀,白板和暗金的距离了。这个东西其实有成品,著名的Visual Leak Detector (http://www.codeproject.com/KB/applications/visualleakdetector.aspx)是也,当然商业化的BoundsChecker也挺好,前提是你有破解和一台彪悍无比的机器。以及,vld和boundschecker不会自杀,好吧,我承认我这里都跑不起来,我人品应该没问题吧。。。。。。
自己写一个也不难,StackWalk64函数给好了,抄抄代码也就搞定了,自己用用还是够的。
一般这个阶段是最消耗时间和耐性的,毕竟有了CallStack也还是要自己一行行的去review code,但同理,实际情况永远比你想象的复杂,比如我明明看到全局统计里面,C/C++内存没有涨,可是任务管理器里面已经涨了200MB了,oh my god,这又是怎么一回事。
是的,有一个重要问题是不能忘记的,这里不但有可控的c/c++,还有不可控的C#和.net。我就知道一个真实的事,有一帮人心比天高的开发了一套很高级的j2ee后台服务,号称。。。(此处省略5000字),上线前听说出事了,内存泄露,好吧,java的内存泄露,后来又听说解决了,怎么解决的呢,买了IBM的purify和一年高级服务,然后直接用高级服务从美国弄了个人过来purify了三天,价格?10w+。
GC我实在是不熟悉,只好又去求教Jeffrey Richter 的 《CLR Via C#》,然后被我发现了 CLR Profiler,所以说什么事情都怕楞的,我这个也是初哥蛮干,选项全开,CLR Profiler一开,挂了。内存一路飚涨,直到“Out of Memory”,无语。后来吧,学乖了只开个基本开关,做几次snapshot马上退出,看着内存从1G飚到3G,再飚下来,心里直念阿弥陀佛你老人家早啊。看看结果我也楞了,20寸的显示器完全不够大的,那位借个65寸的给我使使吧,最后没办法,数据拷回去那看电影那个28的屏幕做分析,勉强够啊也就。真不是一般人玩的起的。.Net还真是出了问题,有几个对象死活没有释放,而且还越来越多,貌似什么eventhandler,我的不懂。后来有人找了个商业的.net memory profiler 出来,马上就好多了,那是腰也直了,眼睛也亮了,腿脚也好了啊,你看看人家,内存没占多少,性能影响极少,自带分析界面,可以snapShot,可以compare,还带智能分析。败家的clr profiler啊。
至此问题基本解决了,其实已经可以喝酒聊天,大家party了。剩下的全是我个人兴趣。。。。。
Problem 3:内存碎片
碎片吧,其实也不算是特大问题,俗话说挤一挤总还有的,实在不行,我报错重启总行吧,反正一个client,又不是Server。这也就是说说,以photoshop这种级别的client,内存开的那叫一个恐怖啊。我这随便操作了两三下,得,malloc次数10w,这,这也太离谱了点。。。。。这不是把可怜的2G空间玩死里捏吗。
对于普通程序不太重要的碎片问题,在一跑就是几个月的Server上,和对内存极度渴求的图像,音乐,视频程序中,就变成了暴风源头,一不小心,那可就是一场台风。
对付碎片问题,绝招有两个,一:用linux,二:用memory pool。
第一个答案其实很,怎么说呢,没办法,做Server的人大概都知道,VC的Malloc效率极低,让人无法忍受,而相反,linux的malloc几乎是你能想到最新最快的,所有许多做linxu的人,很干脆的就是直接用malloc/free,才懒得写那劳什子的memory pool。
malloc其实也很多版本,最初的malloc算法也是种类繁多,各执一词,但据说次从N年前,tcmalloc诞生之后,就王者归来了一把,从此再无硝烟。只是由于tcmalloc多线程比较差,所以之后才有linux的ptmalloc和通用版本的nedmalloc做了一些改进。可以毫不夸张的说,用nedmalloc/hoard用上手之后,一般的程序,也就不用优化来优化去了,也不用考虑啥memory pool了,我想,这也是现在malloc算法名声不显的原因吧。
hoard:http://prisms.cs.umass.edu/emery/index.php?page=hoard
nedmalloc:http://www.nedprod.com/programs/portable/nedmalloc/
这些都是基于C的,我们还有一个后起之秀,基于C++的google-perftools,据说性能还要彪悍一些,由于本人VC6,所以就。。。。。
goole-perftools:http://code.google.com/p/google-perftools/
至于Memory Pool,可谓仁者见仁,智者见智了,一般是用在嵌入式和Server上。其他地方,那就根据需求,量身定做,才会合乎需求,提高效率,不然就要画虎不成反类犬了。用了还不如不用。而且对memory pool的管理算法,本人也是头疼的紧,暂时还没有一个特别有效的办法,不知哪里有达者可以教我。
Problem 4:内存优化
就我个人而言,是不愿放弃碎片问题的,毕竟对于图像程序,大量碎片伤害太大。所以我的最终方案,既不是替换malloc,也不是构造memory pool,而是第三种选择,一个简单的GC。
说是简单,因为我只做了两件事情,内存重用和定期回收。free不再free,只是设了个标志位,遇到还要malloc同样size的内存时,直接return就好。而为了防止内存无限制增长,还做了定期回收,比如30秒或者一分钟回收一次不再使用的内存。
经过测试,这个方案对于内存碎片问题是革命性的(纯属自夸),在30s回收条件下,malloc的次数整整降了一个数量级,原本10w次的malloc,只需要不到8k,就能完成所需了。而内存占用,也并不比傻傻的malloc/free多出多少来,这也是图像程序的性质决定的,往往都是开一些同样大小的内存,malloc次数又是极高。
接下来的问题,就是比较了,这种方案除了减少碎片以外,到底能带来多大的性能提高呢?
最后,希望能实现一个漂亮的GC算法,提高10倍左右的性能和降低2个数量级的malloc/free次数吧,待续。。。。。
推荐阅读
-
理解内存管理:追踪、泄漏和优化
-
搞定App内存问题:一步步分析和优化内存泄漏的方法
-
理解工作流:自动化业务流程管理与Activiti实践" **简述** 工作流(Workflow)是一种利用电脑技术自动化管理业务流程的方式,让不同参与者按既定路径执行任务,确保文档、信息或任务在预设规则下顺利传递,最终达成期望的业务目标。 **核心概念** - **工作流自动化**: 计算机驱动业务流程处理与执行,如在参与者间自动传递文档和任务。 - **目标与应用**: 管理工作流程确保按时、由合适的人执行,同时允许人工介入以增强灵活性。 - **工作流框架示例**: Activiti、JBPM、OSWorkflow 和 Workflow,它们背后通常依赖数据库支持。 - **关键组件**: ProcessEngine 在 Activiti 中扮演核心角色,负责流程实例创建、数据管理和流程监控。 **相关领域** - **业务流程管理 (BPM)**: 一种系统性方法论,聚焦于构建并优化端到端卓越业务流程以提升企业业绩,在EMBA、MBA等商业课程中得到关注。 - **业务流程建模与标记语言 (BPMN)**: 用于绘制业务流程图的工具,探讨其在不同场景下的应用精确度、标准化价值以及未来发展愿景。 **辅助术语** - 流对象 (Flow Objects): BPMN 中用于描述流程中活动、决策、序列和其他元素的具体实现单元。
-
Faster CPython 公布 Python 3.13 计划:优化解释器和内存管理
-
jemalloc 优化 MySQL 和 Nginx 内存管理
-
Magisk 模块:超保护后台模块,通过优化内存管理方法和参数优化,达到超保护后台的效果,在内存足够的情况下基本不杀后台。
-
openEuler郑州用户组成立!openEuler与hyperfusion携手共建河南地区用户生态 - 开幕致辞 超融合操作系统业务总经理、openEuler委员会成员蒋振华先生为本次活动致辞。 在本次活动的致辞中,他提到,作为openEuler社区早期的成员,超融合见证了openEuler从成立到在各行业商业落地,再到跨越生态拐点的过程,感谢openEuler提供了一个全产业链共同创新的平台,共同推动创新技术的商业落地。 同时,本次活动得到了郑州市郑东新区大数据管理局、郑州中原科技城投资服务局的大力支持。 郑东新区大数据管理局曹光远 在活动致辞中表示,openEuler的应用和*应用设施的深度优化,为郑东新区数字化转型提供了安全、可靠、高性能的技术基础;郑州中原科技城招商服务局王林表示,郑东新区欢迎所有openEuler生态相关企业扎根当地,围绕openEuler社区共同发展,形成合力。 openEuler社区及运维功能介绍 openEuler技术委员会委员胡峰 openEuler技术委员会委员胡峰先生在本次活动中介绍了openEuler社区目前发展的整体情况,并重点从技术层面介绍了openEuler的运维功能。 openEuler 晚会 胡峰先生介绍智能运维工具 A-Ops 和 openEuler gala、 阿波罗 Apollo、智能漏洞管理解决方案等新功能,以及涵盖各种运维场景的精品运维组件。在*交流环节,许多用户就目前使用的 openEuler 在*交流环节,许多用户就自己在使用openEuler过程中遇到的一些问题与胡峰先生进行了进一步的交流。 软硬结合,构建多样化算力操作系统 Hyperfusion 基于 openEuler 的基础上,结合自身软硬件技术积累,推出了富讯服务器操作系统 FusionOS FusionOS. FusionOS 首席架构师张海亮 分享了 FusionOS FusionOS首席架构师张海亮分享了FusionOS的软硬件协同优势、卓越的性能和可靠性,以及FusionOS在金融、运营商、*、互联网等行业的实践案例,引起了众多用户的兴趣,分享结束后,不少参会者就FusionOS的特点向讲师提问并进行了交流。
-
理解JVM堆内存管理:剖析关键参数jmap heap中的MinHeapFreeRatio、MaxHeapFreeRatio、MaxHeapSize、NewSize和MaxNewSize
-
玩转Java底层:JMX详解 - jconsole与自定义MBean监控工具的实际应用与区别" 在日常JVM调优中,我们熟知的jconsole工具通过JMX包装的bean以图形化形式展示管理数据,而像jstat和jmap这类内建监控工具则由JVM直接支持。本文将以jconsole为例,深入讲解其实质——基于JMX的MBean功能,包括可视化界面上的bean属性查看和操作调用。 MBeans在jconsole中的体现是那些可观察的组件属性和方法,如上图所示,通过名为"Verbose"的属性能看到其值为false,同时还能直接操作该bean的方法,例如"closeJerryMBean"。 尽管jconsole给我们提供了直观的可视化界面,但请注意,这里的MBean并非固定不变,开发者可根据JMX提供的接口将自己的自定义bean展示到jconsole。以下步骤展示了如何创建并注册一个名为"StudyJavaMBean"的自定义MBean: 1. 首先定义接口`StudyJavaMBean`,接口需遵循MBean规范,即后缀为"MBean"且包含getter方法代表属性,如`getApplicationName`,和无返回值的setter方法代表操作,如`closeJerryMBean`。 ```java public interface StudyJavaMBean { String getApplicationName(); void closeJerryMBean(); } ``` 2. 编写接口的实现类`StudyJavaMBeanImpl`,实现接口中的方法: ```java public class StudyJavaMBeanImpl implements StudyJavaMBean { @Override public String getApplicationName() { return "每天学Java"; } @Override public void closeJerryMBean() { System.out.println("关闭Jerry应用"); } } ``` 3. 在代码中注册自定义MBean,涉及的关键步骤包括: - 获取平台MBeanServer - 定义ObjectName,指定唯一的MBean标识符 - 注册MBean到服务器 - 启动RMI连接器服务,以便jconsole能够访问 ```java public void registerMBean() throws Exception { // ... 具体实现省略 ... } ``` 实际运行注册后的MBean,您将在jconsole中发现并查看自定义bean的属性和调用相关方法。然而,这种方式相较于传统的属性/日志查看和HTTP接口,实用性相对有限,可能存在潜在的安全风险。但不可否认的是,JMX及其MBean机制对于获取操作系统信息、内存状态等关键性能指标仍然具有重要价值。例如: 1. **获取操作系统信息**:通过JMX MBean,可以直接获取到诸如CPU使用率、操作系统版本等系统级信息,这对于资源管理和优化工作具有显著帮助。
-
聊聊内存泄漏、栈溢出和野指针那些事儿(轻松理解版)