我正在尝试弄清楚应该如何使用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);
}
}在大约达到2 2GB的内存使用量(任何CPU)或耗尽我机器的所有物理内存(X64)(16 2GB)之后,上面的代码抛出内存不足异常。
如果我删除cache.Add位,程序也不会抛出异常。如果我在每次添加缓存后都包含对cache.Trim(5)的调用,我会看到它释放了一些内存,并且在任何给定的时间(从cache.GetCount()),它在缓存中保留了大约150个对象。
调用cache.Trim是我的程序的责任吗?如果是这样的话,什么时候应该调用它(比如我的程序怎么知道内存已经满了)?如何计算百分比参数?
注意:我计划在一个长时间运行的windows服务中使用MemoryCache,所以对它进行适当的内存管理是至关重要的。
发布于 2020-06-18 15:07:15
MemoryCache有一个后台线程,它定期估计进程使用了多少内存以及缓存中有多少键。当它认为你正在接近cachememorylimit时,它会裁剪缓存。每次运行这个后台线程时,它都会检查您是否接近限制,并且会在内存紧张的情况下增加轮询频率。
如果您以非常快的速度添加项,后台线程将没有机会运行,并且您可能会在缓存清理和GC运行之前耗尽内存(在x64进程中,这可能会导致巨大的堆大小和数分钟的GC暂停)。已知的trim过程/内存估计也具有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中的作用域类来确保缓存值的线程安全,并避免dispose上的竞争。
https://stackoverflow.com/questions/23969334
复制相似问题