1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
|
// <copyright file="CacheMemoryMonitor.cs" company="Microsoft">
// Copyright (c) 2009 Microsoft Corporation. All rights reserved.
// </copyright>
using System;
using System.Runtime.Caching.Configuration;
using System.Runtime.Caching.Hosting;
using System.Diagnostics.CodeAnalysis;
using System.Security;
using System.Security.Permissions;
using System.Threading;
namespace System.Runtime.Caching {
// CacheMemoryMonitor uses the internal System.SizedReference type to determine
// the size of the cache itselt, and helps us know when to drop entries to avoid
// exceeding the cache's memory limit. The limit is configurable (see ConfigUtil.cs).
internal sealed class CacheMemoryMonitor : MemoryMonitor, IDisposable {
const long PRIVATE_BYTES_LIMIT_2GB = 800 * MEGABYTE;
const long PRIVATE_BYTES_LIMIT_3GB = 1800 * MEGABYTE;
const long PRIVATE_BYTES_LIMIT_64BIT = 1L * TERABYTE;
const int SAMPLE_COUNT = 2;
private static IMemoryCacheManager s_memoryCacheManager;
private static long s_autoPrivateBytesLimit = -1;
private static long s_effectiveProcessMemoryLimit = -1;
private MemoryCache _memoryCache;
private long[] _cacheSizeSamples;
private DateTime[] _cacheSizeSampleTimes;
private int _idx;
private SRefMultiple _sizedRefMultiple;
private int _gen2Count;
private long _memoryLimit;
internal long MemoryLimit {
get { return _memoryLimit; }
}
private CacheMemoryMonitor() {
// hide default ctor
}
internal CacheMemoryMonitor(MemoryCache memoryCache, int cacheMemoryLimitMegabytes) {
_memoryCache = memoryCache;
_gen2Count = GC.CollectionCount(2);
_cacheSizeSamples = new long[SAMPLE_COUNT];
_cacheSizeSampleTimes = new DateTime[SAMPLE_COUNT];
if (memoryCache.UseMemoryCacheManager)
InitMemoryCacheManager(); // This magic thing connects us to ObjectCacheHost magically. :/
InitDisposableMembers(cacheMemoryLimitMegabytes);
}
private void InitDisposableMembers(int cacheMemoryLimitMegabytes) {
bool dispose = true;
try {
_sizedRefMultiple = new SRefMultiple(_memoryCache.AllSRefTargets);
SetLimit(cacheMemoryLimitMegabytes);
InitHistory();
dispose = false;
}
finally {
if (dispose) {
Dispose();
}
}
}
// Auto-generate the private bytes limit:
// - On 64bit, the auto value is MIN(60% physical_ram, 1 TB)
// - On x86, for 2GB, the auto value is MIN(60% physical_ram, 800 MB)
// - On x86, for 3GB, the auto value is MIN(60% physical_ram, 1800 MB)
//
// - If it's not a hosted environment (e.g. console app), the 60% in the above
// formulas will become 100% because in un-hosted environment we don't launch
// other processes such as compiler, etc.
private static long AutoPrivateBytesLimit {
get {
long memoryLimit = s_autoPrivateBytesLimit;
if (memoryLimit == -1) {
bool is64bit = (IntPtr.Size == 8);
long totalPhysical = TotalPhysical;
long totalVirtual = TotalVirtual;
if (totalPhysical != 0) {
long recommendedPrivateByteLimit;
if (is64bit) {
recommendedPrivateByteLimit = PRIVATE_BYTES_LIMIT_64BIT;
}
else {
// Figure out if it's 2GB or 3GB
if (totalVirtual > 2 * GIGABYTE) {
recommendedPrivateByteLimit = PRIVATE_BYTES_LIMIT_3GB;
}
else {
recommendedPrivateByteLimit = PRIVATE_BYTES_LIMIT_2GB;
}
}
// use 60% of physical RAM
long usableMemory = totalPhysical * 3 / 5;
memoryLimit = Math.Min(usableMemory, recommendedPrivateByteLimit);
}
else {
// If GlobalMemoryStatusEx fails, we'll use these as our auto-gen private bytes limit
memoryLimit = is64bit ? PRIVATE_BYTES_LIMIT_64BIT : PRIVATE_BYTES_LIMIT_2GB;
}
Interlocked.Exchange(ref s_autoPrivateBytesLimit, memoryLimit);
}
return memoryLimit;
}
}
public void Dispose() {
SRefMultiple sref = _sizedRefMultiple;
if (sref != null && Interlocked.CompareExchange(ref _sizedRefMultiple, null, sref) == sref) {
sref.Dispose();
}
IMemoryCacheManager memoryCacheManager = s_memoryCacheManager;
if (memoryCacheManager != null) {
memoryCacheManager.ReleaseCache(_memoryCache);
}
}
internal static long EffectiveProcessMemoryLimit {
get {
long memoryLimit = s_effectiveProcessMemoryLimit;
if (memoryLimit == -1) {
memoryLimit = AutoPrivateBytesLimit;
Interlocked.Exchange(ref s_effectiveProcessMemoryLimit, memoryLimit);
}
return memoryLimit;
}
}
protected override int GetCurrentPressure() {
// Call GetUpdatedTotalCacheSize to update the total
// cache size, if there has been a recent Gen 2 Collection.
// This update must happen, otherwise the CacheManager won't
// know the total cache size.
int gen2Count = GC.CollectionCount(2);
SRefMultiple sref = _sizedRefMultiple;
if (gen2Count != _gen2Count && sref != null) {
// update _gen2Count
_gen2Count = gen2Count;
// the SizedRef is only updated after a Gen2 Collection
// increment the index (it's either 1 or 0)
Dbg.Assert(SAMPLE_COUNT == 2);
_idx = _idx ^ 1;
// remember the sample time
_cacheSizeSampleTimes[_idx] = DateTime.UtcNow;
// remember the sample value
_cacheSizeSamples[_idx] = sref.ApproximateSize;
#if DBG
Dbg.Trace("MemoryCacheStats", "SizedRef.ApproximateSize=" + _cacheSizeSamples[_idx]);
#endif
IMemoryCacheManager memoryCacheManager = s_memoryCacheManager;
if (memoryCacheManager != null) {
memoryCacheManager.UpdateCacheSize(_cacheSizeSamples[_idx], _memoryCache);
}
}
// if there's no memory limit, then there's nothing more to do
if (_memoryLimit <= 0) {
return 0;
}
long cacheSize = _cacheSizeSamples[_idx];
// use _memoryLimit as an upper bound so that pressure is a percentage (between 0 and 100, inclusive).
if (cacheSize > _memoryLimit) {
cacheSize = _memoryLimit;
}
// PerfCounter: Cache Percentage Process Memory Limit Used
// = memory used by this process / process memory limit at pressureHigh
// Set private bytes used in kilobytes because the counter is a DWORD
//
int result = (int)(cacheSize * 100 / _memoryLimit);
return result;
}
internal override int GetPercentToTrim(DateTime lastTrimTime, int lastTrimPercent) {
int percent = 0;
if (IsAboveHighPressure()) {
long cacheSize = _cacheSizeSamples[_idx];
if (cacheSize > _memoryLimit) {
percent = Math.Min(100, (int)((cacheSize - _memoryLimit) * 100L / cacheSize));
}
#if PERF
SafeNativeMethods.OutputDebugString(String.Format("CacheMemoryMonitor.GetPercentToTrim: percent={0:N}, lastTrimPercent={1:N}\n",
percent,
lastTrimPercent));
#endif
}
return percent;
}
internal void SetLimit(int cacheMemoryLimitMegabytes) {
long cacheMemoryLimit = cacheMemoryLimitMegabytes;
cacheMemoryLimit = cacheMemoryLimit << MEGABYTE_SHIFT;
//
_memoryLimit = 0;
// VSWhidbey 546381: never override what the user specifies as the limit;
// only call AutoPrivateBytesLimit when the user does not specify one.
if (cacheMemoryLimit == 0 && _memoryLimit == 0) {
// Zero means we impose a limit
_memoryLimit = EffectiveProcessMemoryLimit;
}
else if (cacheMemoryLimit != 0 && _memoryLimit != 0) {
// Take the min of "cache memory limit" and the host's "process memory limit".
_memoryLimit = Math.Min(_memoryLimit, cacheMemoryLimit);
}
else if (cacheMemoryLimit != 0) {
// _memoryLimit is 0, but "cache memory limit" is non-zero, so use it as the limit
_memoryLimit = cacheMemoryLimit;
}
Dbg.Trace("MemoryCacheStats", "CacheMemoryMonitor.SetLimit: _memoryLimit=" + (_memoryLimit >> MEGABYTE_SHIFT) + "Mb");
if (_memoryLimit > 0) {
_pressureHigh = 100;
_pressureLow = 80;
}
else {
_pressureHigh = 99;
_pressureLow = 97;
}
Dbg.Trace("MemoryCacheStats", "CacheMemoryMonitor.SetLimit: _pressureHigh=" + _pressureHigh +
", _pressureLow=" + _pressureLow);
}
[SecuritySafeCritical]
[PermissionSet(SecurityAction.Assert, Unrestricted = true)]
[SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "Grandfathered suppression from original caching code checkin")]
private static void InitMemoryCacheManager() {
if (s_memoryCacheManager == null) {
IMemoryCacheManager memoryCacheManager = null;
IServiceProvider host = ObjectCache.Host;
if (host != null) {
memoryCacheManager = host.GetService(typeof(IMemoryCacheManager)) as IMemoryCacheManager;
}
if (memoryCacheManager != null) {
Interlocked.CompareExchange(ref s_memoryCacheManager, memoryCacheManager, null);
}
}
}
}
}
|