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.
4
5 #include "base/memory/discardable_memory_manager.h"
6
7 #include "base/bind.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"
14
15 namespace base {
16 namespace internal {
17
DiscardableMemoryManager(size_t memory_limit,size_t soft_memory_limit,size_t bytes_to_keep_under_moderate_pressure,TimeDelta hard_memory_limit_expiration_time)18 DiscardableMemoryManager::DiscardableMemoryManager(
19 size_t memory_limit,
20 size_t soft_memory_limit,
21 size_t bytes_to_keep_under_moderate_pressure,
22 TimeDelta hard_memory_limit_expiration_time)
23 : allocations_(AllocationMap::NO_AUTO_EVICT),
24 bytes_allocated_(0u),
25 memory_limit_(memory_limit),
26 soft_memory_limit_(soft_memory_limit),
27 bytes_to_keep_under_moderate_pressure_(
28 bytes_to_keep_under_moderate_pressure),
29 hard_memory_limit_expiration_time_(hard_memory_limit_expiration_time) {
30 BytesAllocatedChanged(bytes_allocated_);
31 }
32
~DiscardableMemoryManager()33 DiscardableMemoryManager::~DiscardableMemoryManager() {
34 DCHECK(allocations_.empty());
35 DCHECK_EQ(0u, bytes_allocated_);
36 }
37
RegisterMemoryPressureListener()38 void DiscardableMemoryManager::RegisterMemoryPressureListener() {
39 AutoLock lock(lock_);
40 DCHECK(base::MessageLoop::current());
41 DCHECK(!memory_pressure_listener_);
42 memory_pressure_listener_.reset(new MemoryPressureListener(base::Bind(
43 &DiscardableMemoryManager::OnMemoryPressure, Unretained(this))));
44 }
45
UnregisterMemoryPressureListener()46 void DiscardableMemoryManager::UnregisterMemoryPressureListener() {
47 AutoLock lock(lock_);
48 DCHECK(memory_pressure_listener_);
49 memory_pressure_listener_.reset();
50 }
51
SetMemoryLimit(size_t bytes)52 void DiscardableMemoryManager::SetMemoryLimit(size_t bytes) {
53 AutoLock lock(lock_);
54 memory_limit_ = bytes;
55 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(
56 Now(), memory_limit_);
57 }
58
SetSoftMemoryLimit(size_t bytes)59 void DiscardableMemoryManager::SetSoftMemoryLimit(size_t bytes) {
60 AutoLock lock(lock_);
61 soft_memory_limit_ = bytes;
62 }
63
SetBytesToKeepUnderModeratePressure(size_t bytes)64 void DiscardableMemoryManager::SetBytesToKeepUnderModeratePressure(
65 size_t bytes) {
66 AutoLock lock(lock_);
67 bytes_to_keep_under_moderate_pressure_ = bytes;
68 }
69
SetHardMemoryLimitExpirationTime(TimeDelta hard_memory_limit_expiration_time)70 void DiscardableMemoryManager::SetHardMemoryLimitExpirationTime(
71 TimeDelta hard_memory_limit_expiration_time) {
72 AutoLock lock(lock_);
73 hard_memory_limit_expiration_time_ = hard_memory_limit_expiration_time;
74 }
75
ReduceMemoryUsage()76 bool DiscardableMemoryManager::ReduceMemoryUsage() {
77 return PurgeIfNotUsedSinceHardLimitCutoffUntilWithinSoftMemoryLimit();
78 }
79
Register(Allocation * allocation,size_t bytes)80 void DiscardableMemoryManager::Register(Allocation* allocation, size_t bytes) {
81 AutoLock lock(lock_);
82 // A registered memory listener is currently required. This DCHECK can be
83 // moved or removed if we decide that it's useful to relax this condition.
84 // TODO(reveman): Enable this DCHECK when skia and blink are able to
85 // register memory pressure listeners. crbug.com/333907
86 // DCHECK(memory_pressure_listener_);
87 DCHECK(allocations_.Peek(allocation) == allocations_.end());
88 allocations_.Put(allocation, AllocationInfo(bytes));
89 }
90
Unregister(Allocation * allocation)91 void DiscardableMemoryManager::Unregister(Allocation* allocation) {
92 AutoLock lock(lock_);
93 AllocationMap::iterator it = allocations_.Peek(allocation);
94 DCHECK(it != allocations_.end());
95 const AllocationInfo& info = it->second;
96
97 if (info.purgable) {
98 size_t bytes_purgable = info.bytes;
99 DCHECK_LE(bytes_purgable, bytes_allocated_);
100 bytes_allocated_ -= bytes_purgable;
101 BytesAllocatedChanged(bytes_allocated_);
102 }
103 allocations_.Erase(it);
104 }
105
AcquireLock(Allocation * allocation,bool * purged)106 bool DiscardableMemoryManager::AcquireLock(Allocation* allocation,
107 bool* purged) {
108 AutoLock lock(lock_);
109 // Note: |allocations_| is an MRU cache, and use of |Get| here updates that
110 // cache.
111 AllocationMap::iterator it = allocations_.Get(allocation);
112 DCHECK(it != allocations_.end());
113 AllocationInfo* info = &it->second;
114
115 if (!info->bytes)
116 return false;
117
118 TimeTicks now = Now();
119 size_t bytes_required = info->purgable ? 0u : info->bytes;
120
121 if (memory_limit_) {
122 size_t limit = 0;
123 if (bytes_required < memory_limit_)
124 limit = memory_limit_ - bytes_required;
125
126 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(now,
127 limit);
128 }
129
130 // Check for overflow.
131 if (std::numeric_limits<size_t>::max() - bytes_required < bytes_allocated_)
132 return false;
133
134 *purged = !allocation->AllocateAndAcquireLock();
135 info->purgable = false;
136 info->last_usage = now;
137 if (bytes_required) {
138 bytes_allocated_ += bytes_required;
139 BytesAllocatedChanged(bytes_allocated_);
140 }
141 return true;
142 }
143
ReleaseLock(Allocation * allocation)144 void DiscardableMemoryManager::ReleaseLock(Allocation* allocation) {
145 AutoLock lock(lock_);
146 // Note: |allocations_| is an MRU cache, and use of |Get| here updates that
147 // cache.
148 AllocationMap::iterator it = allocations_.Get(allocation);
149 DCHECK(it != allocations_.end());
150 AllocationInfo* info = &it->second;
151
152 TimeTicks now = Now();
153 allocation->ReleaseLock();
154 info->purgable = true;
155 info->last_usage = now;
156
157 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(
158 now, memory_limit_);
159 }
160
PurgeAll()161 void DiscardableMemoryManager::PurgeAll() {
162 AutoLock lock(lock_);
163 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(Now(), 0);
164 }
165
IsRegisteredForTest(Allocation * allocation) const166 bool DiscardableMemoryManager::IsRegisteredForTest(
167 Allocation* allocation) const {
168 AutoLock lock(lock_);
169 AllocationMap::const_iterator it = allocations_.Peek(allocation);
170 return it != allocations_.end();
171 }
172
CanBePurgedForTest(Allocation * allocation) const173 bool DiscardableMemoryManager::CanBePurgedForTest(
174 Allocation* allocation) const {
175 AutoLock lock(lock_);
176 AllocationMap::const_iterator it = allocations_.Peek(allocation);
177 return it != allocations_.end() && it->second.purgable;
178 }
179
GetBytesAllocatedForTest() const180 size_t DiscardableMemoryManager::GetBytesAllocatedForTest() const {
181 AutoLock lock(lock_);
182 return bytes_allocated_;
183 }
184
OnMemoryPressure(MemoryPressureListener::MemoryPressureLevel pressure_level)185 void DiscardableMemoryManager::OnMemoryPressure(
186 MemoryPressureListener::MemoryPressureLevel pressure_level) {
187 switch (pressure_level) {
188 case MemoryPressureListener::MEMORY_PRESSURE_MODERATE:
189 PurgeUntilWithinBytesToKeepUnderModeratePressure();
190 return;
191 case MemoryPressureListener::MEMORY_PRESSURE_CRITICAL:
192 PurgeAll();
193 return;
194 }
195
196 NOTREACHED();
197 }
198
199 void
PurgeUntilWithinBytesToKeepUnderModeratePressure()200 DiscardableMemoryManager::PurgeUntilWithinBytesToKeepUnderModeratePressure() {
201 AutoLock lock(lock_);
202
203 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(
204 Now(), bytes_to_keep_under_moderate_pressure_);
205 }
206
207 bool DiscardableMemoryManager::
PurgeIfNotUsedSinceHardLimitCutoffUntilWithinSoftMemoryLimit()208 PurgeIfNotUsedSinceHardLimitCutoffUntilWithinSoftMemoryLimit() {
209 AutoLock lock(lock_);
210
211 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(
212 Now() - hard_memory_limit_expiration_time_, soft_memory_limit_);
213
214 return bytes_allocated_ <= soft_memory_limit_;
215 }
216
217 void DiscardableMemoryManager::
PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(TimeTicks timestamp,size_t limit)218 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(
219 TimeTicks timestamp,
220 size_t limit) {
221 lock_.AssertAcquired();
222
223 size_t bytes_allocated_before_purging = bytes_allocated_;
224 for (AllocationMap::reverse_iterator it = allocations_.rbegin();
225 it != allocations_.rend();
226 ++it) {
227 Allocation* allocation = it->first;
228 AllocationInfo* info = &it->second;
229
230 if (bytes_allocated_ <= limit)
231 break;
232
233 bool purgable = info->purgable && info->last_usage <= timestamp;
234 if (!purgable)
235 continue;
236
237 size_t bytes_purgable = info->bytes;
238 DCHECK_LE(bytes_purgable, bytes_allocated_);
239 bytes_allocated_ -= bytes_purgable;
240 info->purgable = false;
241 allocation->Purge();
242 }
243
244 if (bytes_allocated_ != bytes_allocated_before_purging)
245 BytesAllocatedChanged(bytes_allocated_);
246 }
247
BytesAllocatedChanged(size_t new_bytes_allocated) const248 void DiscardableMemoryManager::BytesAllocatedChanged(
249 size_t new_bytes_allocated) const {
250 TRACE_COUNTER_ID1(
251 "base", "DiscardableMemoryUsage", this, new_bytes_allocated);
252
253 static const char kDiscardableMemoryUsageKey[] = "dm-usage";
254 base::debug::SetCrashKeyValue(kDiscardableMemoryUsageKey,
255 Uint64ToString(new_bytes_allocated));
256 }
257
Now() const258 TimeTicks DiscardableMemoryManager::Now() const {
259 return TimeTicks::Now();
260 }
261
262 } // namespace internal
263 } // namespace base
264