MemoryCache OutOfMemoryException

时间:2014-05-31 11:09:01

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

我试图找出应该如何使用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,因此对其进行适当的内存管理至关重要。

1 个答案:

答案 0 :(得分: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类使缓存的值成为线程安全的,并避免在处置时出现争执。 / p>