1 // <copyright file="CacheMemoryMonitor.cs" company="Microsoft">
2 // Copyright (c) 2009 Microsoft Corporation. All rights reserved.
5 using System.Runtime.Caching.Configuration;
6 using System.Runtime.Caching.Hosting;
7 using System.Diagnostics.CodeAnalysis;
9 using System.Security.Permissions;
10 using System.Threading;
12 namespace System.Runtime.Caching {
13 // CacheMemoryMonitor uses the internal System.SizedReference type to determine
14 // the size of the cache itselt, and helps us know when to drop entries to avoid
15 // exceeding the cache's memory limit. The limit is configurable (see ConfigUtil.cs).
16 internal sealed class CacheMemoryMonitor : MemoryMonitor, IDisposable {
17 const long PRIVATE_BYTES_LIMIT_2GB = 800 * MEGABYTE;
18 const long PRIVATE_BYTES_LIMIT_3GB = 1800 * MEGABYTE;
19 const long PRIVATE_BYTES_LIMIT_64BIT = 1L * TERABYTE;
20 const int SAMPLE_COUNT = 2;
22 private static IMemoryCacheManager s_memoryCacheManager;
23 private static long s_autoPrivateBytesLimit = -1;
24 private static long s_effectiveProcessMemoryLimit = -1;
26 private MemoryCache _memoryCache;
27 private long[] _cacheSizeSamples;
28 private DateTime[] _cacheSizeSampleTimes;
30 private SRefMultiple _sizedRefMultiple;
31 private int _gen2Count;
32 private long _memoryLimit;
34 internal long MemoryLimit {
35 get { return _memoryLimit; }
38 private CacheMemoryMonitor() {
42 internal CacheMemoryMonitor(MemoryCache memoryCache, int cacheMemoryLimitMegabytes) {
43 _memoryCache = memoryCache;
44 _gen2Count = GC.CollectionCount(2);
45 _cacheSizeSamples = new long[SAMPLE_COUNT];
46 _cacheSizeSampleTimes = new DateTime[SAMPLE_COUNT];
47 InitMemoryCacheManager();
48 InitDisposableMembers(cacheMemoryLimitMegabytes);
51 private void InitDisposableMembers(int cacheMemoryLimitMegabytes) {
54 _sizedRefMultiple = new SRefMultiple(_memoryCache.AllSRefTargets);
55 SetLimit(cacheMemoryLimitMegabytes);
66 // Auto-generate the private bytes limit:
67 // - On 64bit, the auto value is MIN(60% physical_ram, 1 TB)
68 // - On x86, for 2GB, the auto value is MIN(60% physical_ram, 800 MB)
69 // - On x86, for 3GB, the auto value is MIN(60% physical_ram, 1800 MB)
71 // - If it's not a hosted environment (e.g. console app), the 60% in the above
72 // formulas will become 100% because in un-hosted environment we don't launch
73 // other processes such as compiler, etc.
74 private static long AutoPrivateBytesLimit {
76 long memoryLimit = s_autoPrivateBytesLimit;
77 if (memoryLimit == -1) {
79 bool is64bit = (IntPtr.Size == 8);
81 long totalPhysical = TotalPhysical;
82 long totalVirtual = TotalVirtual;
83 if (totalPhysical != 0) {
84 long recommendedPrivateByteLimit;
86 recommendedPrivateByteLimit = PRIVATE_BYTES_LIMIT_64BIT;
89 // Figure out if it's 2GB or 3GB
91 if (totalVirtual > 2 * GIGABYTE) {
92 recommendedPrivateByteLimit = PRIVATE_BYTES_LIMIT_3GB;
95 recommendedPrivateByteLimit = PRIVATE_BYTES_LIMIT_2GB;
99 // use 60% of physical RAM
100 long usableMemory = totalPhysical * 3 / 5;
101 memoryLimit = Math.Min(usableMemory, recommendedPrivateByteLimit);
104 // If GlobalMemoryStatusEx fails, we'll use these as our auto-gen private bytes limit
105 memoryLimit = is64bit ? PRIVATE_BYTES_LIMIT_64BIT : PRIVATE_BYTES_LIMIT_2GB;
107 Interlocked.Exchange(ref s_autoPrivateBytesLimit, memoryLimit);
114 public void Dispose() {
115 SRefMultiple sref = _sizedRefMultiple;
116 if (sref != null && Interlocked.CompareExchange(ref _sizedRefMultiple, null, sref) == sref) {
119 IMemoryCacheManager memoryCacheManager = s_memoryCacheManager;
120 if (memoryCacheManager != null) {
121 memoryCacheManager.ReleaseCache(_memoryCache);
125 internal static long EffectiveProcessMemoryLimit {
127 long memoryLimit = s_effectiveProcessMemoryLimit;
128 if (memoryLimit == -1) {
129 memoryLimit = AutoPrivateBytesLimit;
130 Interlocked.Exchange(ref s_effectiveProcessMemoryLimit, memoryLimit);
136 protected override int GetCurrentPressure() {
137 // Call GetUpdatedTotalCacheSize to update the total
138 // cache size, if there has been a recent Gen 2 Collection.
139 // This update must happen, otherwise the CacheManager won't
140 // know the total cache size.
141 int gen2Count = GC.CollectionCount(2);
142 SRefMultiple sref = _sizedRefMultiple;
143 if (gen2Count != _gen2Count && sref != null) {
145 _gen2Count = gen2Count;
147 // the SizedRef is only updated after a Gen2 Collection
149 // increment the index (it's either 1 or 0)
150 Dbg.Assert(SAMPLE_COUNT == 2);
152 // remember the sample time
153 _cacheSizeSampleTimes[_idx] = DateTime.UtcNow;
154 // remember the sample value
155 _cacheSizeSamples[_idx] = sref.ApproximateSize;
157 Dbg.Trace("MemoryCacheStats", "SizedRef.ApproximateSize=" + _cacheSizeSamples[_idx]);
159 IMemoryCacheManager memoryCacheManager = s_memoryCacheManager;
160 if (memoryCacheManager != null) {
161 memoryCacheManager.UpdateCacheSize(_cacheSizeSamples[_idx], _memoryCache);
165 // if there's no memory limit, then there's nothing more to do
166 if (_memoryLimit <= 0) {
170 long cacheSize = _cacheSizeSamples[_idx];
172 // use _memoryLimit as an upper bound so that pressure is a percentage (between 0 and 100, inclusive).
173 if (cacheSize > _memoryLimit) {
174 cacheSize = _memoryLimit;
177 // PerfCounter: Cache Percentage Process Memory Limit Used
178 // = memory used by this process / process memory limit at pressureHigh
179 // Set private bytes used in kilobytes because the counter is a DWORD
184 int result = (int)(cacheSize * 100 / _memoryLimit);
188 internal override int GetPercentToTrim(DateTime lastTrimTime, int lastTrimPercent) {
190 if (IsAboveHighPressure()) {
191 long cacheSize = _cacheSizeSamples[_idx];
192 if (cacheSize > _memoryLimit) {
193 percent = Math.Min(100, (int)((cacheSize - _memoryLimit) * 100L / cacheSize));
197 SafeNativeMethods.OutputDebugString(String.Format("CacheMemoryMonitor.GetPercentToTrim: percent={0:N}, lastTrimPercent={1:N}\n",
206 internal void SetLimit(int cacheMemoryLimitMegabytes) {
207 long cacheMemoryLimit = cacheMemoryLimitMegabytes;
208 cacheMemoryLimit = cacheMemoryLimit << MEGABYTE_SHIFT;
213 // VSWhidbey 546381: never override what the user specifies as the limit;
214 // only call AutoPrivateBytesLimit when the user does not specify one.
215 if (cacheMemoryLimit == 0 && _memoryLimit == 0) {
216 // Zero means we impose a limit
217 _memoryLimit = EffectiveProcessMemoryLimit;
219 else if (cacheMemoryLimit != 0 && _memoryLimit != 0) {
220 // Take the min of "cache memory limit" and the host's "process memory limit".
221 _memoryLimit = Math.Min(_memoryLimit, cacheMemoryLimit);
223 else if (cacheMemoryLimit != 0) {
224 // _memoryLimit is 0, but "cache memory limit" is non-zero, so use it as the limit
225 _memoryLimit = cacheMemoryLimit;
228 Dbg.Trace("MemoryCacheStats", "CacheMemoryMonitor.SetLimit: _memoryLimit=" + (_memoryLimit >> MEGABYTE_SHIFT) + "Mb");
230 if (_memoryLimit > 0) {
239 Dbg.Trace("MemoryCacheStats", "CacheMemoryMonitor.SetLimit: _pressureHigh=" + _pressureHigh +
240 ", _pressureLow=" + _pressureLow);
243 [SecuritySafeCritical]
244 [PermissionSet(SecurityAction.Assert, Unrestricted = true)]
245 [SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "Grandfathered suppression from original caching code checkin")]
246 private static void InitMemoryCacheManager() {
247 if (s_memoryCacheManager == null) {
248 IMemoryCacheManager memoryCacheManager = null;
249 IServiceProvider host = ObjectCache.Host;
251 memoryCacheManager = host.GetService(typeof(IMemoryCacheManager)) as IMemoryCacheManager;
253 if (memoryCacheManager != null) {
254 Interlocked.CompareExchange(ref s_memoryCacheManager, memoryCacheManager, null);