Description
Description
MemoryCache
accepts a PhysicalMemoryLimitPercentage property which is documented as
The percentage of physical memory that the cache can use, expressed as an integer value from 1 to 100. If the cache size exceeds the specified limit, the memory cache implementation removes cache entries.
This implies that if the cache is consuming X% of the machine's physical memory, the cache will start evicting entries to reduce memory pressure.
However, the actual behavior is different: the implementation monitors total system-wide physical memory usage — not the memory used by the cache. As a result, cache entries may be evicted even if the cache is consuming a trivial amount of memory, simply because overall system memory is under pressure.
We discovered this issue in production as the MemoryCache
was wrongly evicting items despite the insignificant cache size and the physicalMemoryLimitPercentage
set to 40%.
Reproduction Steps
- Instantiate a
MemoryCache
instance withphysicalMemoryLimitPercentage
set to a value lower than your machine's current system memory usage (can be found in TaskManager > Performance > Memory). - Periodically add items to a small cache without expiration policies, registering a
RemovedCallback
to log entry removals to the console along with theirRemovedReason
. - Observe that items are being removed from the cache with
RemovedReason
set toEvicted
, despite the cache itself not consuming significant memory.
Repro file added: MemoryCacheTest.txt
Expected behavior
The entries should not be evicted from the cache, because the cache size is very small. It does not add up to the physical memory limit percentage defined.
Actual behavior
Cache entries are being regularly evicted because there is overall memory pressure on the machine.
Regression?
Unlikely to be a regression.
Known Workarounds
Set PhysicalMemoryLimitPercentage
to 0 or a sufficiently high value to minimize unwanted cache evictions triggered by system-wide memory pressure.
Configuration
- Reproable in .NET 8 and .NET Framework 4.8.
- Microsoft Windows 11 Enterprise - 10.0.22631 Build 22631
- x64
- Should not be specific to this configuration.
Other information
The relevant code can be found in PhysicalMemoryMonitor.cs. The Windows implementation uses the GlobalMemoryStatusEx
to retrieve the system-wide physical memory usage, rather than the cache's actual memory footprint.
This seems to be intentional, as noted in the class comment:
PhysicalMemoryMonitor monitors the amount of physical memory used on the machine and helps us determine when to drop entries to avoid paging and GC thrashing.
Therefore, this is likely not a code bug, but rather a documentation issue. The current behavior may lead to confusion for service owners who interpret the logic as tracking cache memory usage, potentially resulting in unexpected cache eviction behavior.
Proposed resolutions:
- Clarify the documentation to reflect the current design, making it explicit that eviction decisions are based on global memory pressure, not cache-specific usage.
- Update the implementation to reflect cache memory usage, if that was the intended behavior, although that would be a breaking change.