【问题标题】:MemoryCache OutOfMemoryExceptionMemoryCache OutOfMemoryException
【发布时间】:2014-07-21 02:00:11
【问题描述】:

我试图弄清楚应该如何使用 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.Trim(5) 的调用,我会看到它释放了一些内存,并且在任何给定时间(来自 cache.GetCount())在缓存中保留了大约 150 个对象。

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

注意:我计划在长期运行的 Windows 服务中使用 MemoryCache,因此对其进行适当的内存管理至关重要。

【问题讨论】:

标签: c# .net caching memory-management windows-services


【解决方案1】:

MemoryCache 有一个后台线程,它会定期估计进程正在使用多少内存以及缓存中有多少键。当它认为你正在接近cachememorylimit 时,它会修剪缓存。每次这个后台线程运行时,它都会检查你离限制有多近,它会在内存压力下增加轮询频率。

如果你非常快速地添加项目,后台线程没有机会运行,并且你可能在缓存可以修剪和 GC 运行之前耗尽内存(在 x64 进程中,这可能会导致大量堆大小和多分钟的 GC 暂停)。修剪过程/内存估计也已知有bugs under some conditions

如果您的程序由于快速加载过多的对象而容易出现内存不足,那么像 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 类来使缓存值线程安全并避免 dispose 竞争。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-05-12
    • 1970-01-01
    • 1970-01-01
    • 2011-09-23
    • 2011-07-14
    • 2012-02-25
    • 2011-05-14
    • 2020-05-21
    相关资源
    最近更新 更多