.NET中的内存问题

3
我有一个C#服务,它监听队列以接收XML消息,并使用XSLTs处理它们并将它们写入数据库。每天大约处理60K个1Mb大小的消息。当处于空闲状态时,内存降至100MB,这非常好。 但是,最近我开始处理12 MB大小的消息。它会使内存爆炸,即使处于空闲状态,内存也会达到500MB左右。有什么建议吗?我不认为存在内存泄漏,因为在处理如此多(60K个1MB)的消息后,它应该已经出现了。

1
当服务处于空闲状态并发生垃圾回收时,我期望内存完全释放。此外,如果该服务接收到许多巨大的消息,则会抛出内存不足异常。 - koumides
1
@koumides:当您的服务变得空闲时,您的内存不会立即被释放,垃圾回收器会根据所进行的分配来优化执行内存释放的最佳时间。 - James
1
你是如何测量内存使用的?任务管理器并不是最好的方式...像CLR Profiler这样的分析器会更好地告诉你分配和使用了多少内存。 - sourcenouveau
可能是C# XSLT转换内存不足的重复问题。 - H H
XSLT转换不是问题。 - koumides
1
@koumides 关于xltt转换不是问题/假设它没有离开模板编译/在内存中加载一段时间...那只是一个猜测,但你仍然不能排除这样的原因;) PS使用分析器。 - eglasius
7个回答

7

看起来完全没有问题,你为什么认为这是个问题呢?

垃圾回收器最终会释放未使用的内存,但这并不意味着当您的服务处于空闲状态时就会立即发生。

Raymond Chen写了一篇很好的文章,解释了垃圾回收的基本思想:

每个人都以错误的方式考虑垃圾回收

然而 - 这只是根据您提供的信息进行的纯粹推测 - 您的XSLT中可能与扩展方法相关的内存泄漏。在每次转换新的XML文档时重新编译样式表可能会导致问题。解决方法很简单:一旦编译,缓存样式表。


5

SIR!放下任务管理器并离开。认真对待.NET中的内存管理,不要追求最小的占用空间,而是要追求效率。它会保留内存而不将其释放回系统。除非出现实际问题(OOM异常、系统问题),否则请抵制 babysit 内存的诱惑。


3
你使用什么措施来衡量内存?有很多可能的措施,但没有一种真正意义上的“已用内存”。由于虚拟内存、镜像共享等因素,情况并不简单。
即使处于空闲状态,它的内存也约为500MB。
大多数进程(我认为这包括.NET)一旦分配给进程就不会将内存释放回操作系统。但如果它没有在使用,则会从物理内存中分页,以允许其他进程利用它。
开始处理大小为12MB的消息。
如果将XML文档扩展为对象模型(例如XmlDocument),则需要更多的内存(许多链接到其他节点)。可以考虑使用不同的模型(XDocumentXPathDocument都更轻量级),或者更好地使用XmlReader处理XML,从而永远不会有完全扩展的对象模型。

2
首先,在系统中添加一些东西,以允许您手动调用垃圾回收以进行调查。CLR不一定会将内存返回给操作系统,除非内存短缺。您应该使用此机制在系统处于静止状态时手动调用垃圾回收,以便您可以查看内存是否下降。(您应该两次调用GC.Collect(2)以确保实际收集具有终结器的对象而不仅仅是终结器。)
如果内存在完整的GC之后降至静止水平,则没有问题:只是.NET不主动释放内存。
如果内存没有下降,则您可能存在某种泄漏。由于您的消息很大,因此它们的文本表示很可能最终出现在大对象堆(LOH)上。这个特定的堆不是紧凑的,这意味着与其他堆相比,更容易泄漏这个内存。
要注意的一件事是字符串内部化:如果您手动内部化字符串,这可能会导致LOH碎片化。

编译器对字符串字面量的内部化是否会导致相同的碎片化? - Matt Briggs
@Matt Briggs:不,编译器实际上并没有执行任何内部化操作,相反,所有字符串字面量都是以这样的方式编译的,即在程序启动时只会创建一个字符串对象。 - Paul Ruane

1

很难说可能是什么问题。在调查内存问题时,我使用Ants Memory Profiler取得了成功。


1

与人们的想法相反,我不认为存在内存泄漏;我会检查内存。


0
我会检查你的事件处理程序。如果你不小心在完成时没有分离它们,似乎很容易创建对象引用,这些引用将不会被GC收集。
我不知道这是否是最好的做法(因为开始和取消事件处理的责任应该落在订阅者身上),但我已经采取了实现IDisposable并使用显式委托字段实例创建我的事件的路线。在处理完毕后,该字段可以设置为null,从而有效地分离所有订阅。
类似于这样:
public class Publisher
    : IDisposable
{
    private EventHandler _somethingHappened;
    public event EventHandler SomethingHappened
    {
        add { _somethingHappened += value; }
        remove { _somethingHappened -= value; }
    }
    protected void OnSomethingHappened(object sender, EventArgs e)
    {
        if (_somethingHappened != null)
            _somethingHappened(sender, e);
    }

    public void Dispose()
    {
        _somethingHappened = null;
    }
}

除非你对发布者有多大的控制,否则你可能需要小心地在订阅者上分离不需要的处理程序:
public class Subscriber
    : IDisposable
{ 
    private Publisher _publisher;
    public Publisher Publisher
    {
        get { return _publisher; }
        set {
            // Detach from the old reference
            DetachEvents(_publisher);
            _publisher = value;
            // Attach to the new
            AttachEvents(_publisher);
        }
    }

    private void DetachEvents(Publisher publisher)
    {
        if (publisher != null)
        {
            publisher.SomethingHappened -= new EventHandler(publisher_SomethingHappened);
        }
    }
    private void AttachEvents(Publisher publisher)
    {
        if (publisher != null)
        {
            publisher.SomethingHappened += new EventHandler(publisher_SomethingHappened);
        }
    }

    void publisher_SomethingHappened(object sender, EventArgs e)
    {
        // DO STUFF
    }

    public void Dispose()
    {
        // Detach from reference
        DetachEvents(Publisher);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接