ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SAP内存管理与Paging调优:MEMORY_NO_MORE_PAGING转储的成因与排查

SAP内存管理与Paging调优:MEMORY_NO_MORE_PAGING转储的成因与排查 1. 一个让系统停滞的内存转储做SAP这么多年ST22几乎是每个运维和开发都不愿多看几眼的界面因为它出现往往意味着系统里有东西坏了。但在所有ST22转储类型里MEMORY_NO_MORE_PAGING是我认为最需要认真对待的一个。它不像是某个程序逻辑写错导致的异常而更像是整台应用服务器的内存体系被挤到了极限最后在“Paging分页”这一步彻底崩了。这个错误出现的时候业务用户的第一感知通常很明确操作到一半屏幕卡住几秒钟后看到“短转储”的红色系统消息提示再往后可能连登录都变慢因为应用服务器的可用资源被大量挤占。对于SAP的运维顾问、ABAP开发人员以及负责系统性能的团队来说掌握这个错误的来龙去脉不只是为了“消转储”更是为了避免生产系统在业务高峰期出现大面积不可用。这篇文章里我会从SAP Paging机制讲起结合我实际排查过的一些案例场景把MEMORY_NO_MORE_PAGING的成因、排查路径、调优思路和代码层面的规避方法完整拆一遍。不管是刚接触SAP Basis的新手还是写了很多年ABAP却对内存管理了解不深的开发这篇文章都值得认真看完。1.1 MEMORY_NO_MORE_PAGING到底是什么从SAP的错误分类来看MEMORY_NO_MORE_PAGING属于“内存空间不足”类错误触发点是ABAP运行时环境在尝试把内存中的数据换出到Paging区域时发现Paging空间已经耗尽或者无法继续扩展于是直接终止当前工作进程。这里的“Paging”不是操作系统级别的虚拟内存换页而是SAP应用服务器内部专门用于保存溢出数据的一块独立区域。这个错误比较棘手的地方在于它表面上指向“Paging空间不够”但实际根源往往在扩展内存Extended Memory的配置、程序对内存的消耗方式甚至并发用户的整体资源占用上。很多时候你打开ST22看到这个错误再往下翻错误分析会发现触发点只不过是一条SELECT语句或者一次内表操作而真正的问题藏在更前面的内存申请链条里。1.2 这个错误的影响范围有多大单次MEMORY_NO_MORE_PAGING转储意味着一个工作进程被终止正在跑的事务或报表直接中断。如果只是个别用户在跑一个异常程序影响还算可控最怕的是刚好在月末结账、物料账运行、MRP批量跑批这种时段出现多个后台作业和在线用户同时把内存顶到上限短时间内会出现连续多个进程被终止整个应用服务器进入半瘫痪状态。我在一次现场支持时遇到过这样的情况某生产系统从上午十点开始半小时内连续产生了几十条MEMORY_NO_MORE_PAGING转储涉及两三个常用报表和某些后台作业。业务部门的反馈是“系统越来越慢什么操作都转圈”。打开ST22一看内存类转储占了绝大多数应用服务器的负载并没有异常高但内存换页的压力已经让所有工作进程都处于濒临崩溃的边界。1.3 谁最该深入了解这个机制如果你只负责SAP业务模块的配置可能一辈子都不用打开ST22但如果你是ABAP开发者写报表时动不动用无条件SELECT把整表数据装进内表那这个错误会在你最意想不到的时候找上门。如果你是Basis或系统管理员内存参数的调整、Paging空间的规划、并发压力的控制都直接关系到系统能否平稳运行。所以这篇文章的受众我认为主要是ABAP开发人员和管理SAP系统的技术人员。运维人员需要知道怎么排查和调参开发人员需要知道怎么写代码才能避免把系统内存吃垮。两边共同把一个机制理解透了生产环境的这套内存体系才能稳定。2. SAP Paging机制与内存体系拆解很多ABAP开发对SAP内存体系的理解其实停留在“内存表放在内存里数据库表放在数据库里”这个层面这并不能解释MEMORY_NO_MORE_PAGING为什么发生。要理解这个错误首先要把SAP应用服务器的内存分区搞清楚。2.1 ABAP运行时内存体系的整体框架SAP应用服务器进程的内存大致可以分成以下几个区域扩展内存Extended MemoryABAP程序运行时的“主战场”内表、变量、对象实例的数据基本都放在这里。系统按照em/initial_size_MB和em/max_size_MB这两个参数控制扩展内存的分配策略。它是一块相对独立的共享内存区域各个工作进程按需从这里申请空间。私有内存Private Memory / Heap Memory每个工作进程还有自己独立的堆内存区域主要用于存放一些不属于ABAP上下文的数据。ABAP程序申请大块内存时尤其是超过扩展内存配置限制后会转向私有堆内存。Roll缓冲Roll Area当工作进程处理完一个请求、被切换去处理另一个请求时当前ABAP上下文的数据不能直接丢需要临时保存到Roll区域。它相当于工作进程之间的“临时寄存处”。Paging分页区域这是存储那些从内存中被“换出”的数据的空间。当扩展内存和Roll区域压力增大系统会把一些暂时不用的数据块写到Paging区域腾出空间给正在活跃的程序片段使用。这四个区域相互配合构成了ABAP程序运行时的完整内存环境。其中Paging和Roll在物理上都对应着应用服务器上的文件如Linux系统下安装目录中的分页文件而扩展内存和私有内存则对应物理内存中的映射区。理解这一点很关键Paging虽然是“内存体系”的一部分但它本质上是有磁盘IO参与的数据交换区速度远比不上真正的物理内存。2.2 Paging在其中的位置与触发路径我用一个生活里的场景来解释Paging的位置。你在一张办公桌上处理文件桌面就是扩展内存正常情况下的工作都在桌上完成。桌面上堆不下的时候你会把一部分不那么急用的文件放进身后的文件柜文件柜就是Roll区域。但文件柜容量也有限再放不下就只能搬到更远处的仓库去这个仓库就是Paging。等你要用仓库里的文件时再跑一趟搬回来。SAP的工作进程在处理用户请求时也是这个流程。程序申请的内存超过了扩展内存的可用额度时系统会尝试把一些数据挪到Paging区一旦Paging空间也满了申请新空间的请求就会失败于是触发MEMORY_NO_MORE_PAGING终止进程。这里有一个容易被忽略的点Paging区域的默认配置通常不是无限大的它受ipc/paging_blocks等参数控制。很多系统的Paging空间从安装时就按当时的业务规模设定数据量增长、程序复杂度上升之后这个空间没有同步扩容结果就是平时勉强够用遇到高峰或者个别内存大户程序时段就直接崩盘。2.3 几个关键参数和调整方向SAP Basis团队最常打交道的几个内存参数和这个错误直接相关em/initial_size_MB扩展内存的初始分配值。这个值太小会导致进程启动时频繁向操作系统申请内存增加开销。em/max_size_MB扩展内存的最大上限。这是单个工作进程能够占用的扩展内存天花板。遇到单个程序内存消耗特别大的情况这个参数决定它能不能继续长胖。abap/heap_area_total所有ABAP工作进程的堆内存总量上限。这个参数全局控制私有堆内存的使用防止所有进程加起来把物理内存吃光。abap/heap_area_dia和abap/heap_area_nondia分别限制Dialog和后台工作进程的堆内存分配量。ipc/paging_blocksPaging文件的总块数。这个参数直接决定了Paging区域的大小调整后需要重启应用服务器生效。rdisp/roll_maxfsRoll缓冲文件的最大大小。这些参数不是独立存在的它们相互牵制。我经常遇到的一个情况是客户觉得内存不够就把em/max_size_MB调得很大但忽略了abap/heap_area_total的总量限制导致单个进程虽然能申请很多扩展内存但所有进程共享的堆内存总量很快见底依然会触发内存类转储。调整内存参数时一定要按“单进程上限”和“全局总量”两个维度一起看缺一不可。2.4 为什么报错的是Paging而不是扩展内存很多第一次接触这个错误的开发会疑惑程序用的内存明明是内表是在扩展内存里的为什么报错却扯到Paging上原因在于SAP的内存管理机制里扩展内存不是一个“只进不出”的区域。系统会定期或者按需把扩展内存中的某些数据块标记为“不活跃”然后移到Paging区。这个动作本身是正常的内存管理行为目的就是给活跃进程腾出足够空间。问题在于当多个进程同时处于高内存消耗状态或者某个进程的内表数据量极大系统需要向Paging区搬运大量的数据块。如果Paging区剩余空间不足搬不出去新的内存申请就无法满足系统只能终止进程。所以MEMORY_NO_MORE_PAGING的本质是“内存压力已经传导到了整个存储链条的最末端”而不是单纯的某个区域空间不足。3. 最常见的三类诱因排查这个错误的时候我总结过一套经验所有MEMORY_NO_MORE_PAGING案例几乎都可以归结到三个方向——程序本身内存消耗失控、系统配置跟不上数据量增长、并发压力集中释放。很多时候还是两个甚至三个因素叠加。3.1 大结果集一次性加载这一类是最容易定位的也是开发阶段最不应该出现的。比如说一个报表程序为了显示全量数据写了一句最简单粗暴的代码SELECT * FROM zflight INTO TABLE DATA(lt_flight).如果zflight表只有几千行问题不大但业务运行几年后这张表可能有上百万行。单次SELECT把所有字段全部拉进内表整个内表在扩展内存中的占用可能达到几百兆甚至上GB。一条用户请求就把一个工作进程的扩展内存吃掉了大半再来两个并发用户系统就处在悬崖边缘。我在帮客户做健康检查时不止一次在自定义开发程序里看到这类没有WHERE条件、没有分页、没有数量限制的全量查询。写的时候很爽上线之后就是定时炸弹。数据量小的时候爆炸不了等表长大到一定程度每个月的批处理时间一到就会集中爆发。3.2 代码层面的内存失控比全表SELECT更隐蔽的是代码逻辑导致的内存失控。比较典型的是递归调用没有终止条件或者递归层次过深。ABAP里写递归函数不是不行但要非常克制。我见过一个用来展开BOM结构的程序设计的时候是循环调用函数自身但物料主数据里存在循环嵌套的异常场景时递归没有出口一层套一层每层都往内表里追加数据几秒钟内就能把扩展内存和Paging空间全部顶满。ST22报出来的错误里这种场景的调用栈会特别长一拉鼠标滑轮都滑不到底。还有一种隐蔽的内存失控是字符串拼接。在循环里反复做字符串拼接每次拼接都生成一个新的字符串对象旧对象还没来得及释放新的又来了。内表行数本身不大但每一行的CLOB字段都在膨胀整个程序的内存占用就会呈几何级数增长。在没有GC压力的情况下这类程序跑得越久死得越快。3.3 并发高峰的放大器效应单个程序的内存消耗如果在正常范围但同一时段有大量用户同时执行高消耗操作就会把系统的整体内存预算瞬间打穿。这种情况在每个月末、季度末尤其明显财务做月结、物料做账、销售做统计报表所有高消耗任务挤在同一个时间窗口。我遇到过一个很典型的场景某个查询报表单次执行只需要150MB的扩展内存本身不算离谱。但月末上午20多个业务用户几乎同时打开了这个报表加上后台还有三四个批处理作业在跑瞬间需要的总内存就超额了。系统不得不频繁地把数据往Paging区搬运Paging区IO压力激增最终多个工作进程同时报MEMORY_NO_MORE_PAGING。并发这个因素经常被忽略因为单看任何一个程序都觉得没有问题。但系统的内存预算不是按“单个程序”来分配的而是按“所有并发进程的总和”来分配的。一个平时正常的程序在错误的时间和其他程序叠加就可能成为压垮系统的最后一根稻草。4. ST22排查流程与现场处理遇到MEMORY_NO_MORE_PAGING不要慌按照一套固定的排查流程走基本都能把问题缩小到一个很具体的范围。这套流程花不了多长时间但能帮你避免在错误的方向上浪费时间。4.1 读懂ST22转储里的关键字段进入ST22之后你会看到系统里的转储列表。按时间排序找到MEMORY_NO_MORE_PAGING类型的记录双击进入详情页。这里有几个信息是排查看重中之后的首先是“What happened?”区域系统会用一段文字描述到底发生了什么。通常会出现类似“The current process was terminated due to a memory shortage”的描述。这里告诉你的是直接原因而不是根本原因所以不要在这句话上纠结太久。其次是“错误分析”Error Analysis这里会给出更具体的信息比如是哪个进程、在哪个内存区域申请空间时失败。再往下“终止位置”会列出程序名、包含对象名、行号这是你定位代码的关键线索。最后是“活动类型”Activity type它会告诉你是Dialog请求、RFC调用还是后台作业。这个信息对于判断影响范围很有用——如果是后台作业直接找到作业所属的计划任务重点关注批处理时间窗口如果是Dialog请求需要关注前端用户的操作路径和并发情况。4.2 定位触发程序的思路拿到程序名和行号之后用SE80或SE93打开对应程序源码找到出问题的那一行。绝大多数情况下这一行附近会有一句SELECT语句、一个内表操作或者一个函数调用。不要只看这一行要把前后至少二三十行代码都看清楚理解这段代码在执行什么业务逻辑内表承载了多少数据量。如果转储信息里给出的程序名是一个很通用的报表并且涉及多个用户同时使用那么问题可能不只是代码本身还需要结合并发情况判断。如果是一个只在批处理中运行的函数那么就要重点分析这个函数是否在某个数据维度上做了全量处理。我一般还会顺手查一下数据库表的记录数。直接用SE11查看表的“附加信息”或者用事务代码SE14统计一下表的当前记录量。如果程序查询的主表已经有几百万行而程序没有分页处理基本上可以断定根因了。4.3 用ST02/AL08/ST06组合确认内存状态定位到具体程序之后还需要确认系统当前的内存状态以判断是偶发还是趋势性问题。ST02显示的是SAP内存区域的使用情况。重点看Paging和Extended Memory两块的当前使用率。如果Paging的使用率已经接近90%以上说明系统的Paging空间本身就吃紧如果Extended Memory的使用率经常冲到高位说明扩展内存配置可能偏小。AL08可以查看当前登录用户和工作进程的内存占用。如果发现某几个用户对应的进程内存占用明显偏高可以用它锁定嫌疑用户。ST06则是看操作系统层面的物理内存使用情况避免只盯着SAP内部参数而忽略了物理服务器本身的内存是否已经耗尽。这三个工具配合使用基本可以拼出完整的画面程序吃内存、进程占资源、系统Paging空间紧张三者重叠在一起。4.4 现场止血的几种办法生产系统出了问题第一要务是恢复业务运行。在确定根因之前可以先做以下临时处置如果转储集中在个别高消耗程序上最直接的办法是临时限制该程序的可执行用户范围或者通知业务部门暂停使用该事务。如果是后台作业导致先锁定或暂停相关作业等业务低峰期再执行。如果系统整体内存压力非常大可以考虑扩展Paging空间的参数并重启应用服务器。但是要注意这个操作会中断所有在线用户连接必须在跟业务确认过的情况下再执行。如果只是个别程序问题重启服务器来解决不是最优方案因为你要么回到原样要么在没有解决问题的情况下去调大参数属于治标不治本。4.5 常见问题速查现象可能原因优先排查单个报表执行到一半就转储程序一次性加载了大结果集ST22定位程序行号、检查SQL和表数据量特定后台作业批量运行时报错批处理程序内存消耗失控查看作业对应的程序代码、监控批处理时段内存占用多个用户同时操作时并发转储并发高峰放大了内存压力AL08看占用、错峰安排、限制并发系统刚启动时正常运行几天后频繁转储Paging空间被碎片化占满检查ST02的Paging使用率、考虑重启释放调整了 em/max_size_MB 仍然报错未考虑 abap/heap_area_total 全局限制检查所有堆内存参数、重新规划内存预算这个速查表是我在平时工作中沉淀下来的覆盖面不一定全但用来做第一轮筛选基本够用。5. 根治方案参数调优与代码规范找到问题只是第一步最重要的是把问题解决掉并且防止它再次发生。根治措施需要从参数和代码两个层面同时进行只做任何一个方面都可能留下隐患。5.1 参数调整的边界与原则先说明一点调参这件事不是越大越好。内存参数配高了单个用户的内存占用上限变大但如果物理机总内存不够反而会导致操作系统级别的交换系统整体性能下降甚至引发Linux的OOM Killer。所以调参前必须知道服务器的物理内存总量并且给操作系统和其他非SAP应用留出足够余量。我个人在做内存参数规划时会先算一笔账。假设一台物理机有64GB内存操作系统和其他服务大约占用10GB那么SAP可用的物理内存预算大约在50GB左右。这个50GB要分配给所有SAP工作进程、缓冲区、共享内存和Paging/ Roll文件。理论上所有进程的最大内存占用之和不应该超过这个预算。具体到参数上我会按以下顺序逐项核对确认em/max_size_MB的值确保单个Dialog进程的扩展内存上限足够支撑正常业务程序。检查abap/heap_area_dia和abap/heap_area_nondia的分配确保Dialog进程和后台进程之间的内存额度不要失衡。核对abap/heap_area_total的值让它在所有进程堆内存之和的合理范围内。重点查看ipc/paging_blocks的当前配置通过操作系统上Paging文件的当前大小来判断是否有扩展空间。每一项参数修改都建议在开发或测试环境验证后再上生产。动态参数可以通过RZ11直接改静态参数改完需要重启实例才能生效。很多系统管理员在调整em/max_size_MB后没有重启以为已经生效结果过了几天又报同样的错误这种坑我也踩过几次。5.2 代码层面的通用优化清单ABAP程序的内存管理很大程度取决于编写者的习惯。我归纳了一些在实际工作中验证过有效的优化方向照着做基本能避免掉进内存陷阱。第一条尽量避免全表无条件SELECT。如果一张表的行数已经超过几十万程序的逻辑应该是分批获取数据而不是一口气全部加载。举个典型的例子DATA: lt_batch TYPE TABLE OF zflight, lv_last_id TYPE zflight-id VALUE 0, lv_more TYPE abap_bool VALUE abap_true. WHILE lv_more abap_true. SELECT * FROM zflight WHERE id lv_last_id ORDER BY id UP TO 1000 ROWS INTO TABLE lt_batch. IF lines( lt_batch ) 1000. lv_more abap_false. ENDIF. LOOP AT lt_batch INTO DATA(ls_flight). lv_last_id ls_flight-id. 业务处理 ENDLOOP. CLEAR lt_batch. ENDWHILE.这样每一批只占用一小段内存处理完一批释放一批程序的峰值内存不会随表数据量线性上涨。第二条能在数据库层完成的计算就交给数据库。数据库通常有强大的聚合和过滤能力把几百行结果通过网络传到应用层和把几万行原始数据拉到应用层再计算内存差的不是一个数量级。使用SUM、COUNT、MAX、MIN等聚合函数就是一个很好的选择。第三条小心使用FOR ALL ENTRIES。这个语法在SAP开发中很常用但有一个经典陷阱如果FOR ALL ENTRIES后面的驱动内表为空生成的SQL会变成无WHERE条件的全表扫描而且是“没有任何过滤条件”的那种。所以使用前务必判空否则一张千万级表会被毫无防备地完整读一遍。第四条动态数据不要放内表里反复处理。有些业务逻辑需要多次遍历数据把数据放在数据库表还是内表要权衡清楚。少量数据用内表没问题大数据量还反复在应用层循环扫描内存和CPU都扛不住。第五条避免在循环里做字符串拼接或频繁的内表操作。字符串拼接会反复生成临时对象APPEND到内表本身不贵但每轮循环都触发内表扩容就会造成不必要的内存拷贝。这些都是ABAP开发中比较隐蔽的内存床点。5.3 运维侧的分流与监控代码调优和参数调整都完成后运维侧还需要建立长效机制。对高消耗程序建议在业务侧做并发控制。可以通过调整SAP的作业调度策略把多个重负载的批处理作业错开执行时间避免同时涌入。在线用户方面如果同一个报表在固定时段出现并发峰值也可以考虑在应用层做用户数限制或排队机制。监控方面我建议定期记录ST02里Paging和扩展内存的使用率趋势至少每周一次。如果发现使用率在逐步爬升说明业务数据量或程序的负载在增长就要提前评估是否需要调参或优化代码而不是等它报警。ST22的转储也应该有定期的复盘机制。不要只是点进去看一眼就关掉至少要统计一下每个转储类型的数量特别是内存类的转储是否有上升趋势。如果MEMORY_NO_MORE_PAGING从一周一次变成一天一次系统的内存健康状况就是在恶化必须提前介入。5.4 避免走入“无脑调参”的误区最后想特别提醒一句不要一看到MEMORY_NO_MORE_PAGING就把em/max_size_MB翻倍。这种做法治标不治本而且可能带来新的问题。举例来说如果程序本身的写法就是把一张百万行大表一次读进内表你把em/max_size_MB从2GB调到4GB它确实不会再立刻报Paging不足了但这只是把程序曾经产生过大内存消耗的事实掩盖了。到了高峰期多个用户同时执行这种程序物理内存很快就会被吃满届时出现的问题可能不只是SAP的短转储而是整个服务器的操作系统内存崩溃。正确的顺序是先看代码、再看并发、最后才调参数。代码优化是根本并发控制是辅助参数调整是兜底。这个顺序反了问题只会越拖越重。最后说点我的实际体会这些年处理过不少系统内存相关的故障我越来越觉得大多数MEMORY_NO_MORE_PAGING的根源都不是“系统配置出错”而是“系统在诞生之初没想过会有这么大的数据量”。一个当初写着方便的全表查询随着业务发展变成了内存杀手一个当初从别的程序拷贝过来的处理逻辑因为数据增长而成了系统的定时炸弹。所以每次排查完一个内存问题我都会把整个处理过程记录下来包括当时的ST22截图、内存参数、程序代码、优化前后的对比。累积下来的这些案例就是最好的知识库。以后再遇到类似问题翻一下之前的处理记录能省去大量重复排查的时间。另外如果你是在开发阶段就看到了这篇文章那我要多说一句写代码的时候永远假设你操作的表会变成当前行数的100倍。用这个标准去审视每一句SQL和每一次内表操作很多内存问题根本不会发生。系统的稳定性不只是在运维阶段维护出来的更是开发阶段设计出来的。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进