MemoryCache OutOfMemoryException

Joh*_*ohn 5 .net c# caching windows-services memory-management

我试图找出应该如何使用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);
    }        
}
Run Code Online (Sandbox Code Playgroud)

在大约达到2GB的内存使用量(任何CPU)或消耗了我所有机器的物理内存(x64)(16GB)之后,上面引发内存不足异常.

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

正在调用cache.Trim我的程序是否有责任?如果是这样的话什么时候应该被调用(比如我的程序怎么知道内存已经满了)?你如何计算百分比参数?

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

Ale*_*eck 3

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

如果您非常快地添加项目,则后台线程没有机会运行,并且在缓存可以修剪和 GC 可以运行之前您可能会耗尽内存(在 x64 进程中,这可能会导致巨大的堆大小和多分钟的时间) GC 暂停)。众所周知,修剪进程/内存估计在某些情况下也存在错误。

如果您的程序由于快速加载过多的对象而容易出现内存不足,那么像 LRU 缓存这样具有有限大小的东西是一个更好的策略。LRU 通常使用基于项目计数的策略来逐出最近最少使用的项目。

我编写了 TLRU(一种时间感知最近最少使用策略)的线程安全实现,您可以轻松地将其用作 ConcurrentDictionary 的替代品。

它可以在 Github 上找到: https: //github.com/bitfaster/BitFaster.Caching

Install-Package BitFaster.Caching
Run Code Online (Sandbox Code Playgroud)

对于您的程序来说,使用它看起来就像这样,并且不会耗尽内存(取决于您的文件有多大):

 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();
    }
 }
Run Code Online (Sandbox Code Playgroud)

如果您确实想避免内存不足,还应该考虑将文件读入RecyclableMemoryStream中,并使用 BitFaster 中的 Scoped 类来使缓存值线程安全并避免处置时出现竞争。