1 // Copyright 2013 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_provider.h"
8 #include "base/containers/hash_tables.h"
9 #include "base/containers/mru_cache.h"
10 #include "base/debug/trace_event.h"
11 #include "base/lazy_instance.h"
12 #include "base/memory/discardable_memory.h"
13 #include "base/synchronization/lock.h"
14 #include "base/sys_info.h"
21 static base::LazyInstance<DiscardableMemoryProvider>::Leaky g_provider =
22 LAZY_INSTANCE_INITIALIZER;
24 // If this is given a valid value via SetInstanceForTest, this pointer will be
25 // returned by GetInstance rather than |g_provider|.
26 static DiscardableMemoryProvider* g_provider_for_test = NULL;
28 // This is admittedly pretty magical. It's approximately enough memory for two
30 static const size_t kDefaultDiscardableMemoryLimit = 32 * 1024 * 1024;
31 static const size_t kDefaultBytesToReclaimUnderModeratePressure =
32 kDefaultDiscardableMemoryLimit / 2;
36 DiscardableMemoryProvider::DiscardableMemoryProvider()
37 : allocations_(AllocationMap::NO_AUTO_EVICT),
39 discardable_memory_limit_(kDefaultDiscardableMemoryLimit),
40 bytes_to_reclaim_under_moderate_pressure_(
41 kDefaultBytesToReclaimUnderModeratePressure),
42 memory_pressure_listener_(
43 base::Bind(&DiscardableMemoryProvider::NotifyMemoryPressure)) {
46 DiscardableMemoryProvider::~DiscardableMemoryProvider() {
47 DCHECK(allocations_.empty());
48 DCHECK_EQ(0u, bytes_allocated_);
52 DiscardableMemoryProvider* DiscardableMemoryProvider::GetInstance() {
53 if (g_provider_for_test)
54 return g_provider_for_test;
55 return g_provider.Pointer();
59 void DiscardableMemoryProvider::SetInstanceForTest(
60 DiscardableMemoryProvider* provider) {
61 g_provider_for_test = provider;
65 void DiscardableMemoryProvider::NotifyMemoryPressure(
66 MemoryPressureListener::MemoryPressureLevel pressure_level) {
67 switch (pressure_level) {
68 case MemoryPressureListener::MEMORY_PRESSURE_MODERATE:
69 DiscardableMemoryProvider::GetInstance()->Purge();
71 case MemoryPressureListener::MEMORY_PRESSURE_CRITICAL:
72 DiscardableMemoryProvider::GetInstance()->PurgeAll();
79 void DiscardableMemoryProvider::SetDiscardableMemoryLimit(size_t bytes) {
81 discardable_memory_limit_ = bytes;
82 EnforcePolicyWithLockAcquired();
85 void DiscardableMemoryProvider::SetBytesToReclaimUnderModeratePressure(
88 bytes_to_reclaim_under_moderate_pressure_ = bytes;
89 EnforcePolicyWithLockAcquired();
92 void DiscardableMemoryProvider::Register(
93 const DiscardableMemory* discardable, size_t bytes) {
95 DCHECK(allocations_.Peek(discardable) == allocations_.end());
96 allocations_.Put(discardable, Allocation(bytes));
99 void DiscardableMemoryProvider::Unregister(
100 const DiscardableMemory* discardable) {
101 AutoLock lock(lock_);
102 AllocationMap::iterator it = allocations_.Peek(discardable);
103 if (it == allocations_.end())
106 if (it->second.memory) {
107 size_t bytes = it->second.bytes;
108 DCHECK_LE(bytes, bytes_allocated_);
109 bytes_allocated_ -= bytes;
110 free(it->second.memory);
112 allocations_.Erase(it);
115 scoped_ptr<uint8, FreeDeleter> DiscardableMemoryProvider::Acquire(
116 const DiscardableMemory* discardable,
118 AutoLock lock(lock_);
119 // NB: |allocations_| is an MRU cache, and use of |Get| here updates that
121 AllocationMap::iterator it = allocations_.Get(discardable);
122 CHECK(it != allocations_.end());
124 if (it->second.memory) {
125 scoped_ptr<uint8, FreeDeleter> memory(it->second.memory);
126 it->second.memory = NULL;
128 return memory.Pass();
131 size_t bytes = it->second.bytes;
133 return scoped_ptr<uint8, FreeDeleter>();
135 if (discardable_memory_limit_) {
137 if (bytes < discardable_memory_limit_)
138 limit = discardable_memory_limit_ - bytes;
140 PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit);
143 bytes_allocated_ += bytes;
145 return scoped_ptr<uint8, FreeDeleter>(static_cast<uint8*>(malloc(bytes)));
148 void DiscardableMemoryProvider::Release(
149 const DiscardableMemory* discardable,
150 scoped_ptr<uint8, FreeDeleter> memory) {
151 AutoLock lock(lock_);
152 // NB: |allocations_| is an MRU cache, and use of |Get| here updates that
154 AllocationMap::iterator it = allocations_.Get(discardable);
155 CHECK(it != allocations_.end());
157 DCHECK(!it->second.memory);
158 it->second.memory = memory.release();
160 EnforcePolicyWithLockAcquired();
163 void DiscardableMemoryProvider::PurgeAll() {
164 AutoLock lock(lock_);
165 PurgeLRUWithLockAcquiredUntilUsageIsWithin(0);
168 bool DiscardableMemoryProvider::IsRegisteredForTest(
169 const DiscardableMemory* discardable) const {
170 AutoLock lock(lock_);
171 AllocationMap::const_iterator it = allocations_.Peek(discardable);
172 return it != allocations_.end();
175 bool DiscardableMemoryProvider::CanBePurgedForTest(
176 const DiscardableMemory* discardable) const {
177 AutoLock lock(lock_);
178 AllocationMap::const_iterator it = allocations_.Peek(discardable);
179 return it != allocations_.end() && it->second.memory;
182 size_t DiscardableMemoryProvider::GetBytesAllocatedForTest() const {
183 AutoLock lock(lock_);
184 return bytes_allocated_;
187 void DiscardableMemoryProvider::Purge() {
188 AutoLock lock(lock_);
190 if (bytes_to_reclaim_under_moderate_pressure_ == 0)
194 if (bytes_to_reclaim_under_moderate_pressure_ < discardable_memory_limit_)
195 limit = bytes_allocated_ - bytes_to_reclaim_under_moderate_pressure_;
197 PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit);
200 void DiscardableMemoryProvider::PurgeLRUWithLockAcquiredUntilUsageIsWithin(
204 "DiscardableMemoryProvider::PurgeLRUWithLockAcquiredUntilUsageIsWithin",
207 lock_.AssertAcquired();
209 for (AllocationMap::reverse_iterator it = allocations_.rbegin();
210 it != allocations_.rend();
212 if (bytes_allocated_ <= limit)
214 if (!it->second.memory)
217 size_t bytes = it->second.bytes;
218 DCHECK_LE(bytes, bytes_allocated_);
219 bytes_allocated_ -= bytes;
220 free(it->second.memory);
221 it->second.memory = NULL;
225 void DiscardableMemoryProvider::EnforcePolicyWithLockAcquired() {
226 lock_.AssertAcquired();
228 bool exceeded_bound = bytes_allocated_ > discardable_memory_limit_;
229 if (!exceeded_bound || !bytes_to_reclaim_under_moderate_pressure_)
233 if (bytes_to_reclaim_under_moderate_pressure_ < discardable_memory_limit_)
234 limit = bytes_allocated_ - bytes_to_reclaim_under_moderate_pressure_;
236 PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit);
239 } // namespace internal