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/adapters.h"
9 #include "base/containers/hash_tables.h"
10 #include "base/containers/mru_cache.h"
11 #include "base/debug/crash_logging.h"
12 #include "base/debug/trace_event.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/synchronization/lock.h"
19 DiscardableMemoryManager::DiscardableMemoryManager(
21 size_t soft_memory_limit
,
22 TimeDelta hard_memory_limit_expiration_time
)
23 : allocations_(AllocationMap::NO_AUTO_EVICT
),
25 memory_limit_(memory_limit
),
26 soft_memory_limit_(soft_memory_limit
),
27 hard_memory_limit_expiration_time_(hard_memory_limit_expiration_time
) {
28 BytesAllocatedChanged(bytes_allocated_
);
31 DiscardableMemoryManager::~DiscardableMemoryManager() {
32 DCHECK(allocations_
.empty());
33 DCHECK_EQ(0u, bytes_allocated_
);
36 void DiscardableMemoryManager::SetMemoryLimit(size_t bytes
) {
38 memory_limit_
= bytes
;
39 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(
40 Now(), memory_limit_
);
43 void DiscardableMemoryManager::SetSoftMemoryLimit(size_t bytes
) {
45 soft_memory_limit_
= bytes
;
48 void DiscardableMemoryManager::SetHardMemoryLimitExpirationTime(
49 TimeDelta hard_memory_limit_expiration_time
) {
51 hard_memory_limit_expiration_time_
= hard_memory_limit_expiration_time
;
54 void DiscardableMemoryManager::ReleaseFreeMemory() {
55 TRACE_EVENT0("base", "DiscardableMemoryManager::ReleaseFreeMemory");
58 size_t bytes_allocated_before_releasing_memory
= bytes_allocated_
;
59 for (auto& entry
: allocations_
) {
60 Allocation
* allocation
= entry
.first
;
61 AllocationInfo
* info
= &entry
.second
;
66 // Skip if memory is still resident, otherwise purge and adjust
67 // |bytes_allocated_|.
68 if (allocation
->IsMemoryResident())
71 size_t bytes_purgable
= info
->bytes
;
72 DCHECK_LE(bytes_purgable
, bytes_allocated_
);
73 bytes_allocated_
-= bytes_purgable
;
74 info
->purgable
= false;
78 if (bytes_allocated_
!= bytes_allocated_before_releasing_memory
)
79 BytesAllocatedChanged(bytes_allocated_
);
82 bool DiscardableMemoryManager::ReduceMemoryUsage() {
83 return PurgeIfNotUsedSinceHardLimitCutoffUntilWithinSoftMemoryLimit();
86 void DiscardableMemoryManager::ReduceMemoryUsageUntilWithinLimit(size_t bytes
) {
88 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(Now(),
92 void DiscardableMemoryManager::Register(Allocation
* allocation
, size_t bytes
) {
94 DCHECK(allocations_
.Peek(allocation
) == allocations_
.end());
95 allocations_
.Put(allocation
, AllocationInfo(bytes
));
98 void DiscardableMemoryManager::Unregister(Allocation
* allocation
) {
100 AllocationMap::iterator it
= allocations_
.Peek(allocation
);
101 DCHECK(it
!= allocations_
.end());
102 const AllocationInfo
& info
= it
->second
;
105 size_t bytes_purgable
= info
.bytes
;
106 DCHECK_LE(bytes_purgable
, bytes_allocated_
);
107 bytes_allocated_
-= bytes_purgable
;
108 BytesAllocatedChanged(bytes_allocated_
);
110 allocations_
.Erase(it
);
113 bool DiscardableMemoryManager::AcquireLock(Allocation
* allocation
,
115 AutoLock
lock(lock_
);
116 // Note: |allocations_| is an MRU cache, and use of |Get| here updates that
118 AllocationMap::iterator it
= allocations_
.Get(allocation
);
119 DCHECK(it
!= allocations_
.end());
120 AllocationInfo
* info
= &it
->second
;
125 TimeTicks now
= Now();
126 size_t bytes_required
= info
->purgable
? 0u : info
->bytes
;
130 if (bytes_required
< memory_limit_
)
131 limit
= memory_limit_
- bytes_required
;
133 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(now
,
137 // Check for overflow.
138 if (std::numeric_limits
<size_t>::max() - bytes_required
< bytes_allocated_
)
141 *purged
= !allocation
->AllocateAndAcquireLock();
142 info
->purgable
= false;
143 info
->last_usage
= now
;
144 if (bytes_required
) {
145 bytes_allocated_
+= bytes_required
;
146 BytesAllocatedChanged(bytes_allocated_
);
151 void DiscardableMemoryManager::ReleaseLock(Allocation
* allocation
) {
152 AutoLock
lock(lock_
);
153 // Note: |allocations_| is an MRU cache, and use of |Get| here updates that
155 AllocationMap::iterator it
= allocations_
.Get(allocation
);
156 DCHECK(it
!= allocations_
.end());
157 AllocationInfo
* info
= &it
->second
;
159 TimeTicks now
= Now();
160 allocation
->ReleaseLock();
161 info
->purgable
= true;
162 info
->last_usage
= now
;
164 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(
168 void DiscardableMemoryManager::PurgeAll() {
169 AutoLock
lock(lock_
);
170 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(Now(), 0);
173 bool DiscardableMemoryManager::IsRegisteredForTest(
174 Allocation
* allocation
) const {
175 AutoLock
lock(lock_
);
176 AllocationMap::const_iterator it
= allocations_
.Peek(allocation
);
177 return it
!= allocations_
.end();
180 bool DiscardableMemoryManager::CanBePurgedForTest(
181 Allocation
* allocation
) const {
182 AutoLock
lock(lock_
);
183 AllocationMap::const_iterator it
= allocations_
.Peek(allocation
);
184 return it
!= allocations_
.end() && it
->second
.purgable
;
187 size_t DiscardableMemoryManager::GetBytesAllocatedForTest() const {
188 AutoLock
lock(lock_
);
189 return bytes_allocated_
;
192 bool DiscardableMemoryManager::
193 PurgeIfNotUsedSinceHardLimitCutoffUntilWithinSoftMemoryLimit() {
194 AutoLock
lock(lock_
);
196 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(
197 Now() - hard_memory_limit_expiration_time_
, soft_memory_limit_
);
199 return bytes_allocated_
<= soft_memory_limit_
;
202 void DiscardableMemoryManager::
203 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(
206 lock_
.AssertAcquired();
208 size_t bytes_allocated_before_purging
= bytes_allocated_
;
209 for (auto& entry
: base::Reversed(allocations_
)) {
210 Allocation
* allocation
= entry
.first
;
211 AllocationInfo
* info
= &entry
.second
;
213 if (bytes_allocated_
<= limit
)
216 bool purgable
= info
->purgable
&& info
->last_usage
<= timestamp
;
220 size_t bytes_purgable
= info
->bytes
;
221 DCHECK_LE(bytes_purgable
, bytes_allocated_
);
222 bytes_allocated_
-= bytes_purgable
;
223 info
->purgable
= false;
227 if (bytes_allocated_
!= bytes_allocated_before_purging
)
228 BytesAllocatedChanged(bytes_allocated_
);
231 void DiscardableMemoryManager::BytesAllocatedChanged(
232 size_t new_bytes_allocated
) const {
234 "base", "DiscardableMemoryUsage", this, new_bytes_allocated
);
236 static const char kDiscardableMemoryUsageKey
[] = "dm-usage";
237 base::debug::SetCrashKeyValue(kDiscardableMemoryUsageKey
,
238 Uint64ToString(new_bytes_allocated
));
241 TimeTicks
DiscardableMemoryManager::Now() const {
242 return TimeTicks::Now();
245 } // namespace internal