引言

以个人的经验,在.Net下所有的OOM问题都是有解的,但有一个唯一的例外,就是GC大对象堆的碎片化问题。.Net的GC程序有一个致命缺陷,就是从来不对大对象堆LOH(Large Object Heap)进行内存整理。随着时间的推移,一个持续运行的程序必然面对LOH碎片化的困境。如果程序足够好,例如只有很少量的大对象或者创建大对象的频率足够低,那么LOH碎片化的几率就会降低,但也仅仅是降低而已。对于一个实际的企业应用程序,由于不可能避免大对象的分配,也就不可能避免LOH的碎片化问题,从而也就无法从根本上避免OOM问题。以后有时间再详细探讨.Net GC程序的各个特性,本文先点到即止。

问题现象

大约在08年1月份的时候,我们多次遭遇OOM问题。通过windbg对内存dump文件进行分析,发现LOH碎片化是引发OOM问题的主因。

首先,用!clrstack检查Managed Code调用栈,发现OOM异常的线程最后是在创建某对象。用kb检查Native Code调用栈,发现是GC Heap分配内存失败:

检查各个Heap的分布情况,可以发现SOH(Small Object Heap)基本已耗用殆尽:

但LOH空闲空间还有约110M,而LOH总大小约144M,即LOH空闲率平均高达76%。在GC触发OOM的情况下,GC Heap中还有大量空闲空间,抛开深层次的内部机制不谈,单纯从这一现象看,不能不说是GC程序的失败。究其原因,就是LOH碎片化严重,并且由于LOH不能被压缩,也就无法释放占用的内存空间供GC扩展Segment使用,结果SOH最终耗用殆尽。

通过SOS EX的扩展命令!dumpgen 3可以方便地查看大对象堆中各个对象的情况,发现非常多的对象都是string类型,内容为Html页面片段,大小从100k到900k不等。用do命令可以详查某个string对象的情况:

在内存中为什么会有如此多的Html页面片段呢?难道WebForm的输出不是使用流吗?

问题原因

通过研究发现,这些String对象是Ajax在处理过程中产生的临时对象,具体代码位置在UpdatePanel的RenderChildren方法中:

问题就在于调用了HtmlTextWriter.InnerWriter.ToString方法,这会把base.RenderChildren方法所形成的Html页面内容整个输出为一个字符串。如果页面尺寸较大,该String对象就会进入大对象堆。由于这是一个底层方法,每次基于Ajax的页面交互都会走到这个方法,使用频度较高,所以长时间使用或并发活动较多就会造成LOH碎片化。

使用UpdatePanel是一种快捷地将传统asp.net webform改造为ajax应用的方法,可以快速实现页面无闪烁的效果,改进客户体验。但这种方案会付出额外的性能开销,因为基本上webform页面生命周期的各个阶段还是都会走到(细节上略有差异)。Webform原有消耗都少不了,而Ajax框架还要增加很多额外处理,例如对Render内容增加特定标记,以便Ajax客户端JS脚本可以解析,进而完成动态更改DOM节点的工作等。而由于上述缺陷的存在,只要使用UpdatePanel,就免不了要面对LOH碎片化的困境。

问题解法

UpdatePanel需要获取子控件的Render内容,并增加额外处理。为此目的,使用了StringWriter来记录Render内容,而StringWriter内部使用StringBuilder来存储所有Write方法写入的数据。在.Net 2.0上解这个问题,要稍微费一些功夫,原因在于.Net 2.0的StringBuilder其实现存在缺陷:

.Net 2.0的StringBuilder,内部的存储结构就是一个简单的string对象。当内部的string对象其容量不足以容纳新的Append数据时,就需要扩展容量,扩展方式为容量加倍。在容量扩展后,老的string对象及新Append的数据要拷贝到新的内存区域。这个过程中,会生成新的string对象及废弃老的string对象。如果string对象的尺寸已经大于85k,那么每次容量扩展就会有两个string对象进入到LOH中。本质上,相对于直接用“+”方式连接两个string对象,StringBuilder只是大幅降低了临时string对象的生成频率,而并不能彻底规避临时string对象的生成。

由于这个原因,在.Net 2.0上,UpdatePanel一旦使用StringWriter,其实早在base.RenderChildren的过程中就已经无法避免LOH碎片了,并不局限于最后调用那个StringWriter.ToString方法。因此,在.Net 2.0上解决这个问题,必须避免使用StringWriter,而靠实现一种不会出现Large Object问题的Stream来解决。

在.Net 4.0上,这个问题的解决方案变得简单了,因为.Net 4.0实现的StringBuilder彻底规避了临时string对象的生成,并且整个处理过程中不会出现Large Object。这样,代码就可以沿用StringWriter,只需为PageRequestManager的EncodeString方法增加一个参数重载的形式:

internal static void EncodeString(TextWriter writer, string type, string id, StringBuilder builder);

在该方法内部,通过StringBuilder的CopyTo方法把字符串内容分段拷贝到一个Buffer中,并逐个写入流。

后记

在08年4月份的时候,我们已经整理过这个问题的描述并反馈给微软公司。但查看最新的.Net 4.0版本,这个缺陷竟然仍然没有修复。或许,在.Net 2.0下,的确有一些修复的难度。但在.Net 4.0下,也就是十几行代码的事情,这就有些不能理解了。

当然,一切问题的根源在于GC不能应对LOH碎片化的情况,会出现OOM这种致命的错误。如果.Net的GC程序能对LOH做更好的处理,那也就用不着大家如此如履薄冰般地编写程序了。

转载于:https://www.cnblogs.com/hbzhang/archive/2011/12/03/2272852.html

论MS-Ajax导致的大对象堆碎片化问题相关推荐

  1. C# 大型对象堆学习总结

    大型对象堆揭秘 http://blog.csdn.net/jfkidear/article/details/18358551 CLR 全面透彻解析 大型对象堆揭秘 Maoni Stephens 目录 ...

  2. .Net 垃圾回收和大对象处理

    英文原文:Maoni Stephens,编译:赵玉开(@玉开Sir) CLR垃圾回收器根据所占空间大小划分对象.大对象和小对象的处理方式有很大区别.比如内存碎片整理 -- 在内存中移动大对象的成本是昂 ...

  3. 关于.net的垃圾回收和大对象处理_标记

    CLR垃圾回收器根据所占空间大小划分对象.大对象和小对象的处理方式有很大区别.比如内存碎片整理 -- 在内存中移动大对象的成本是昂贵的,让我们研究一下垃圾回收器是如何处理大对象的,大对象对程序性能有哪 ...

  4. 记录一次大对象导致的Java堆内存溢出问题

    问题描述 前几天早上出现一后台项目无法登陆的情况,排查发现新生代和老年代都占用100%,FullGC次数大概有100多次,最终出现OOM. 重启Tomcat后,至13点,FullGC的次数达到31次. ...

  5. Java内存配太大导致fullgc_记一次因为短命大对象导致fullGC的问题

    写在前面 java内存申请和释放均是由jvm在控制.而释放往往会出现各种各样的问题,经常一个引用没处理好就引起内存泄漏,最后引发OOM.如果发生在重要业务系统还可能出现严重的生产事故. 因此内存使用一 ...

  6. 记一次生产大对象导致的OOM让架构师连夜排查解决

    为何半夜告警电话狂打不停,为何上线用户投诉不断,是道德的沦丧还是人性的扭曲,NO,是代码的缺陷. Java8的JVM内存管理中,大对象生成直接放入老年代的,当老年代空间不足,就会进行FullGC,频繁 ...

  7. 16-内存分配与回收策略-对象优先分配Eden+大对象进老年代

    1.对象优先在Eden分配 大多数情况下, 对象在新生代Eden区中分配. 当Eden区没有足够空间进行分配时, 虚拟机将发起一次Minor GC.HotSpot虚拟机提供了-XX: +PrintGC ...

  8. jvm深入理解:内存分配与回收策略(优先在Eden分配、大对象直接进入老年代、长期存活的对象将进入老年代、动态对象年龄判定、空间分配担保)

    出入:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) Java技术体系的自动内存管理,最根本的目标是自动化地解决两个问题:自动给对象分配内存以及自动回收分配给对象的内存. 象的内存分配,从 ...

  9. java堆对象_无法为对象堆保留足够的空间

    回答(25) 2 years ago 使用 -XX:MaxHeapSize=512m (或您需要的任何大数字)运行JVM(或简称 -Xmx512m ) 2 years ago 这也可能是因为在32位H ...

最新文章

  1. Xamarin XAML语言教程XAML文件结构与解析XAML
  2. Linux下 /dev/null 21 相关知识说明
  3. oracle opaque_transform,oracle databse link
  4. 解决Ubuntu下切换到root用户后没有声音问题
  5. java tostring 库_java重寫toString()方法
  6. 《C程序设计语言》笔记 (五) 指针与数组
  7. qcow2镜像转换为iso_电子数据镜像格式的转换,以qcow2转raw为例
  8. 字幕滚动c语言程序,MFC实现字幕滚动效果
  9. 由《30天自制操作系统》引发的漫画创作
  10. 人脸对齐(四)--CLM算法及概率图模型改进
  11. 欧姆龙plc的I/O存储器区详解(Omron FINS)
  12. pcs7服务器没有报警信息,PCS7服务器报警重启(工程师培训).pdf
  13. Docker 自动启动和容器自动启动
  14. puppet的使用:ERB模板介绍
  15. html图片旋转3种方式—— CSS3 transform
  16. 计算机域名(domain name)
  17. 介绍旅游网站建设与优化技巧
  18. 虎年啦,小老虎带你4.5分钟彻底掌握Linux中的创建-目录-文件(mkdir-touch)
  19. java如果判断文件夹或文件不存在就创建文件夹或文件
  20. static应用知识:代码块

热门文章

  1. 哈工大计算机学院历史,历史沿革
  2. 鸿蒙开源源码,基于鸿蒙系统开源项目OpenHarmony源码静态分析
  3. exec vs sp_executesql
  4. 2018CHD-ACM新生赛(正式赛)E.解救迷茫的草滩小王子
  5. springcloud记录篇6-分布式配置中心
  6. 浅析MySQL二进制日志
  7. Python学习总结之四 -- 这就是Python的字典
  8. Delphi 与 DirectX 之 DelphiX(25): TDIB.Blur();
  9. 企业如何快速应对市场环境的不断变化
  10. Django中datetime类型的相关操作(记录一下)