最近有一个项目,在服务器上非常吃内存,多的时候,都占了10G内存了。后来在IIS里面给加了限制,到6G内存的时候就自动回收。
最近闲下来,转储了几个dump文件,用windbg来分析,发现占内存的75%全是字符串。
因为这个项目是一个接口项目,主要对外提供XML和JSON格式数据,用到的字符串也非常多。经常循环多了之后,最终生成出来的字符串会比较大。
用windbg分析,达到85K大字符串,在内存中有近4000多个。
(Server上CPU有24核),所以有24个堆。
超过85K的对象会被放在LOH大对象堆里面,这个堆里面会被GC回收吗?
我知道GC回收的时候,会从0代堆开始,然后把还在引用的放到1代堆里面,当扫描1代堆的时候,把有引用的放到2代堆里面。
但是GC一般会多久回收一次,在什么情况下会去回收二代堆呢?我内存中的对象数量达到4000万个的时候,回收是不是会非常慢。
垃圾收集器周期性的执行内存清理工作,一般在以下情况出现时垃圾收集器将会启动:
(1)内存不足溢出时,更确切地应该说是第 0代对象充满时。
(2)调用 GC.Collect 方法强制执行垃圾回收。 (3)Windows报告内存不足时,CLR 将强制执行垃圾回收。
(4)CLR 卸载AppDomain 时,GC将对所有代龄的对象执行垃圾回收。
(5)其他情况,例如物理内存不足,超出短期存活代的内存段门限,运行主机拒绝分配存
等等。
GC 将着手进行内存清理,
当内存释放之前GC会首先检查终止化链表中是否有记录来决定在释放内存之前执行非托管资源的清理工作,然后才执行内存释放。 同时,微软强烈建议不要通过 GC.Collect方法来强制执行垃圾收集,因为那会妨碍 GC本身的工作方式,通过Collect会使对象代龄不断提升,扰乱应用程序的内存使用。只有在明确知道有大量对象停止引用时,才考虑使用 GC.Collect 方法来调用收集器。
GC在性能优化:
垃圾收集器将托管堆中的对象分为三代,分别为:0、1和2。在 CLR 初始化时,会选择为三代设置不同的阙值容量,一般分配为:第0 代大约 256KB,第1 代2MB,第2 代10MB,显然,容量越大效率越低,而 GC 收集器会自动调节其阙值容量来提升执行效率,第0 代对象的回收效率肯定是最高的。
随着对象的不断创建,垃圾收集再次启动时则只会检查 0代对象,并回收 0代垃圾对象。而1 代对象由于未达到预定的 1 代容量阙值,则不会进行垃圾回收操作,从而有效的提高了垃圾收集的效率,这就是代龄机制在垃圾回收中的性能优化作用。
与代码的编写是否规范有很大关系,代码中是否存在随意使用+等连接字符串的操作呢?我以前的公司禁止使用+连接字符串或处理字符串一定要使用类似stringbuilder类的东西。
要找到具体原因,可以模拟请求或用其它压力测试工具。注释,观察,再注释,再观察...
@沧海一杰: 再一个看看你的程序中是否引用了非托管对象,这个需要手动释放相关内存的。
我以前发的博问里有内存优化的问题,有兴趣可以参考下:
代码写的有些地方不规范,用了stringbuilder的append方法了,里面还是用的+号连接的字符串。现在只能是发现一处,改一处了。
LOH(Large Object Heap)堆,用于分配大对象实例。如果引用类型对象的实例大小不小于85000字节时,该实例将被分配到LOH堆上,而LOH堆不会被压缩,而且只在完全GC回收时被回收。
1 可以肯定LOH堆是会被回收的,
2 多久回收一次:对于托管资源,什么时候回收是由GC控制的,而非托管资源则是由程序员自己控制的
3 什么情况会回收二代堆:每次GC运行时,针对 0 1 2这三代进行回收,0至1代的总容量总是保持在16M 而第2 代则可以达到几百M到几G而且回收效率比较低,通常要好几秒
LZ可参考文章:http://www.cnblogs.com/springyangwc/archive/2011/06/13/2080149.html
毕竟 本人水平有限,如有不对之处LZ见谅啊~
大对象应该gc的2代中分配的,具体可用GC.GetGeneration(你的大对象); 看看返回的是不是2,如果是2的,就是2代中分配的,想你这样的,是不是应该考虑,在某些任务之后,垃圾回收一下呢?
另外,在config文件中,configuration下增加runtime元素 <gsServer enabled="true" />,这样,服务器会多开启一个线程,在运行时可以回收对象
兄弟,此程序设计中极有可能有问题.
程序结构是有些问题。但是这个程序历史遗留下来的有些东西,没人敢动手去改。 没文档,以前的人都走了,你懂的。
@oo縼箻ㄗs.鋒: 给你出一招,写一个客户端程序,如果发现内存超过阀值就讲IIS重启(修改web.config)
@秦时明月-Moon.Net塑造Orm经典: 在IIS的应用程序池中加限制,也一样的。到达6G内存的时候,会回收应用程序池,释放内存。
哥哥,你的内存放着不用也是不用,你限制6G内存,那剩下的4G内存干啥呢?当内存不够用的时候GC自然会去回收2代对象,自然也会去回收那些大对象,GC没有回收说明现在内存还足够用。就拿SQL Server来说吧,我服务器24G内存,SQL Server不管用不用一下子全占满,但是影响性能吗,显然不会,SQL Server仍然很快,而且吃的内存表示缓存够多查询反而更快,你限制了内存反而跑的慢。很多时候你就让GC处理就好了。
服务器上可不只这一个程序,还有其它的应用呢,这一个程序占内存多了,其它web网站也跟着慢了。
我在想,你们的网站里面应该有报表的功能,而报表应该是从后台拼字符串拼出来的,这样的话内存肯定占用很多。可以尝试代码实现,加载几次报表,然后调一下GC回收
这个真没有。统计功能有另外的统计系统。这个系统只是单纯的提供XML和Json接口的。字符串拼接的地方,大多都改成stringbuilder了,找到了一个大字 符串使用的,修改之后下降了1G内存。后来找到代码中有一个递归调用,没有退出条件。相当于死循环了。目前系统内存使用大概在3G左右。
1、LOH是在full gc时触发的,楼上有人讲过了。
2、你的字符串,!gcroot看的结果是什么?是否有某些链条没有切断,导致无法回收?如某个static变量,如Cache
3、XML接口,你是否用了XmlSerializer?是否根据type缓存了XmlSerializer?你看一下这个blog:http://www.cnblogs.com/juqiang/archive/2008/01/15/1039936.html
神呀,用webservice返回的DataSet接口,肯定会用这个了。因为dataset就是xml序列化的呀。原来他内部有自己的缓存机制。牛,我现在内存优化下去很多了,但是这个Xml序列化的,慢慢想办法给换掉。3Q
@oo縼箻ㄗs.鋒:
官方解决办法:http://support.microsoft.com/kb/886385/en-us
代码给你一个:
private static Dictionary<Type, XmlSerializer> xmlSerializerCache = new Dictionary<Type, XmlSerializer>(); private static object sync = new object(); public static XmlSerializer GetXmlSerializerByType(Type type) { XmlSerializer xs = null; if (false == xmlSerializerCache.ContainsKey(type)) { lock (sync) { if (false == xmlSerializerCache.ContainsKey(type)) { xs = new XmlSerializer(type); xmlSerializerCache.Add(type, xs); } } } else { xs = xmlSerializerCache[type]; } return xs; }