内存缓存OutOfMemoryException

6

我正在尝试弄清楚如何使用MemoryCache来避免内存不足异常。我来自ASP.Net背景,其中缓存管理自己的内存使用情况,因此我期望MemoryCache也会这样做。但实际上并非如此,就像我制作的下面的测试程序所示:

class Program
{
    static void Main(string[] args)
    {
        var cache = new MemoryCache("Cache");

        for (int i = 0; i < 100000; i++)
        {
            AddToCache(cache, i);
        }


        Console.ReadLine();
    }

    private static void AddToCache(MemoryCache cache, int i)
    {
        var key = "File:" + i;
        var contents = System.IO.File.ReadAllBytes("File.txt");
        var policy = new CacheItemPolicy
        {
            SlidingExpiration = TimeSpan.FromHours(12)
        };

        policy.ChangeMonitors.Add(
                new HostFileChangeMonitor(
                    new[] { Path.GetFullPath("File.txt") }
                    .ToList()));

        cache.Add(key, contents, policy);
        Console.Clear();
        Console.Write(i);
    }        
}

在使用大约2GB内存(任意CPU)或消耗所有机器物理内存(x64)(16GB)后,以上代码会抛出内存不足异常。

如果我删除cache.Add部分,程序将不会抛出任何异常。如果我在每次cache添加后包含对cache.Trim(5)的调用,我看到它释放了一些内存,并且在任何给定时间保留了大约150个对象在缓存中(从cache.GetCount()获取)。

调用cache.Trim是我的程序的责任吗?如果是,那么应该在什么时候调用它(例如,我的程序如何知道内存正在变满)?如何计算百分比参数?

注意:我计划在长时间运行的Windows服务中使用MemoryCache,因此它具有适当的内存管理至关重要。


https://dev59.com/8Ww05IYBdhLWcg3w_W1O - Ohad Schneider
你找到了一个好的答案吗? - rollsch
1个回答

3
MemoryCache有一个后台线程,定期估计进程使用的内存量以及缓存中有多少个键。当它认为你接近cachememorylimit时,它会修剪缓存。每次这个后台线程运行时,它都会检查你离限制还有多远,并在内存压力下增加轮询频率。
如果你很快地添加项目,后台线程就没有机会运行,缓存无法修剪和GC无法运行,你可能会耗尽内存(在x64进程中,这可能导致大量堆大小和多分钟的GC暂停)。修剪过程/内存估计在某些情况下也已知存在错误
如果你的程序容易因快速加载过多对象而出现内存不足的情况,像LRU缓存这样的有界大小的东西是更好的策略。LRU通常使用基于项数的策略来驱逐最近最少使用的项。
我编写了一个线程安全的 TLRU(一种时间感知最近最少使用策略)实现,您可以很容易地将其用作 ConcurrentDictionary 的替代品。
它在 Github 上可用,链接为:https://github.com/bitfaster/BitFaster.Caching
Install-Package BitFaster.Caching

使用它看起来会像这样,对于您的程序,并且它不会耗尽内存(取决于您的文件有多大):
 class Program
 {
    static void Main(string[] args)
    {
        int capacity = 80;
        TimeSpan timeToLive = TimeSpan.FromMinutes(5);
        var lru = new ConcurrentTLru<int, byte[]>(capacity, timeToLive);

        for (int i = 0; i < 100000; i++)
        {
            var value = lru.GetOrAdd(1, (k) => System.IO.File.ReadAllBytes("File.txt"));
        }


        Console.ReadLine();
    }
 }

如果你真的想避免内存耗尽,你还应该考虑将文件读入RecyclableMemoryStream中,并使用BitFaster中的Scoped类使缓存值线程安全,避免在释放时出现竞争。


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