1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/memory/discardable_memory_manager.h"
8 #include "base/containers/hash_tables.h"
9 #include "base/containers/mru_cache.h"
10 #include "base/debug/crash_logging.h"
11 #include "base/debug/trace_event.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/synchronization/lock.h"
18 DiscardableMemoryManager::DiscardableMemoryManager(
20 size_t bytes_to_keep_under_moderate_pressure)
21 : allocations_(AllocationMap::NO_AUTO_EVICT),
23 memory_limit_(memory_limit),
24 bytes_to_keep_under_moderate_pressure_(
25 bytes_to_keep_under_moderate_pressure) {
26 BytesAllocatedChanged();
29 DiscardableMemoryManager::~DiscardableMemoryManager() {
30 DCHECK(allocations_.empty());
31 DCHECK_EQ(0u, bytes_allocated_);
34 void DiscardableMemoryManager::RegisterMemoryPressureListener() {
36 DCHECK(base::MessageLoop::current());
37 DCHECK(!memory_pressure_listener_);
38 memory_pressure_listener_.reset(new MemoryPressureListener(base::Bind(
39 &DiscardableMemoryManager::OnMemoryPressure, Unretained(this))));
42 void DiscardableMemoryManager::UnregisterMemoryPressureListener() {
44 DCHECK(memory_pressure_listener_);
45 memory_pressure_listener_.reset();
48 void DiscardableMemoryManager::SetMemoryLimit(size_t bytes) {
50 memory_limit_ = bytes;
51 EnforcePolicyWithLockAcquired();
54 void DiscardableMemoryManager::SetBytesToKeepUnderModeratePressure(
57 bytes_to_keep_under_moderate_pressure_ = bytes;
60 void DiscardableMemoryManager::Register(Allocation* allocation, size_t bytes) {
62 // A registered memory listener is currently required. This DCHECK can be
63 // moved or removed if we decide that it's useful to relax this condition.
64 // TODO(reveman): Enable this DCHECK when skia and blink are able to
65 // register memory pressure listeners. crbug.com/333907
66 // DCHECK(memory_pressure_listener_);
67 DCHECK(allocations_.Peek(allocation) == allocations_.end());
68 allocations_.Put(allocation, AllocationInfo(bytes));
71 void DiscardableMemoryManager::Unregister(Allocation* allocation) {
73 AllocationMap::iterator it = allocations_.Peek(allocation);
74 DCHECK(it != allocations_.end());
75 const AllocationInfo& info = it->second;
78 size_t bytes_purgable = info.bytes;
79 DCHECK_LE(bytes_purgable, bytes_allocated_);
80 bytes_allocated_ -= bytes_purgable;
81 BytesAllocatedChanged();
83 allocations_.Erase(it);
86 bool DiscardableMemoryManager::AcquireLock(Allocation* allocation,
89 // Note: |allocations_| is an MRU cache, and use of |Get| here updates that
91 AllocationMap::iterator it = allocations_.Get(allocation);
92 DCHECK(it != allocations_.end());
93 AllocationInfo* info = &it->second;
98 size_t bytes_required = info->purgable ? 0u : info->bytes;
102 if (bytes_required < memory_limit_)
103 limit = memory_limit_ - bytes_required;
105 PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit);
108 // Check for overflow.
109 if (std::numeric_limits<size_t>::max() - bytes_required < bytes_allocated_)
112 *purged = !allocation->AllocateAndAcquireLock();
113 info->purgable = false;
114 if (bytes_required) {
115 bytes_allocated_ += bytes_required;
116 BytesAllocatedChanged();
121 void DiscardableMemoryManager::ReleaseLock(Allocation* allocation) {
122 AutoLock lock(lock_);
123 // Note: |allocations_| is an MRU cache, and use of |Get| here updates that
125 AllocationMap::iterator it = allocations_.Get(allocation);
126 DCHECK(it != allocations_.end());
127 AllocationInfo* info = &it->second;
129 allocation->ReleaseLock();
130 info->purgable = true;
131 EnforcePolicyWithLockAcquired();
134 void DiscardableMemoryManager::PurgeAll() {
135 AutoLock lock(lock_);
136 PurgeLRUWithLockAcquiredUntilUsageIsWithin(0);
139 bool DiscardableMemoryManager::IsRegisteredForTest(
140 Allocation* allocation) const {
141 AutoLock lock(lock_);
142 AllocationMap::const_iterator it = allocations_.Peek(allocation);
143 return it != allocations_.end();
146 bool DiscardableMemoryManager::CanBePurgedForTest(
147 Allocation* allocation) const {
148 AutoLock lock(lock_);
149 AllocationMap::const_iterator it = allocations_.Peek(allocation);
150 return it != allocations_.end() && it->second.purgable;
153 size_t DiscardableMemoryManager::GetBytesAllocatedForTest() const {
154 AutoLock lock(lock_);
155 return bytes_allocated_;
158 void DiscardableMemoryManager::OnMemoryPressure(
159 MemoryPressureListener::MemoryPressureLevel pressure_level) {
160 switch (pressure_level) {
161 case MemoryPressureListener::MEMORY_PRESSURE_MODERATE:
164 case MemoryPressureListener::MEMORY_PRESSURE_CRITICAL:
172 void DiscardableMemoryManager::Purge() {
173 AutoLock lock(lock_);
175 PurgeLRUWithLockAcquiredUntilUsageIsWithin(
176 bytes_to_keep_under_moderate_pressure_);
179 void DiscardableMemoryManager::PurgeLRUWithLockAcquiredUntilUsageIsWithin(
183 "DiscardableMemoryManager::PurgeLRUWithLockAcquiredUntilUsageIsWithin",
187 lock_.AssertAcquired();
189 size_t bytes_allocated_before_purging = bytes_allocated_;
190 for (AllocationMap::reverse_iterator it = allocations_.rbegin();
191 it != allocations_.rend();
193 Allocation* allocation = it->first;
194 AllocationInfo* info = &it->second;
196 if (bytes_allocated_ <= limit)
201 size_t bytes_purgable = info->bytes;
202 DCHECK_LE(bytes_purgable, bytes_allocated_);
203 bytes_allocated_ -= bytes_purgable;
204 info->purgable = false;
208 if (bytes_allocated_ != bytes_allocated_before_purging)
209 BytesAllocatedChanged();
212 void DiscardableMemoryManager::EnforcePolicyWithLockAcquired() {
213 PurgeLRUWithLockAcquiredUntilUsageIsWithin(memory_limit_);
216 void DiscardableMemoryManager::BytesAllocatedChanged() const {
217 TRACE_COUNTER_ID1("base", "DiscardableMemoryUsage", this, bytes_allocated_);
219 static const char kDiscardableMemoryUsageKey[] = "dm-usage";
220 base::debug::SetCrashKeyValue(kDiscardableMemoryUsageKey,
221 Uint64ToString(bytes_allocated_));
224 } // namespace internal