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 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 33 DiscardableMemoryManager::~DiscardableMemoryManager() { 34 DCHECK(allocations_.empty()); 35 DCHECK_EQ(0u, bytes_allocated_); 36 } 37 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 46 void DiscardableMemoryManager::UnregisterMemoryPressureListener() { 47 AutoLock lock(lock_); 48 DCHECK(memory_pressure_listener_); 49 memory_pressure_listener_.reset(); 50 } 51 52 void DiscardableMemoryManager::SetMemoryLimit(size_t bytes) { 53 AutoLock lock(lock_); 54 memory_limit_ = bytes; 55 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired( 56 Now(), memory_limit_); 57 } 58 59 void DiscardableMemoryManager::SetSoftMemoryLimit(size_t bytes) { 60 AutoLock lock(lock_); 61 soft_memory_limit_ = bytes; 62 } 63 64 void DiscardableMemoryManager::SetBytesToKeepUnderModeratePressure( 65 size_t bytes) { 66 AutoLock lock(lock_); 67 bytes_to_keep_under_moderate_pressure_ = bytes; 68 } 69 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 76 bool DiscardableMemoryManager::ReduceMemoryUsage() { 77 return PurgeIfNotUsedSinceHardLimitCutoffUntilWithinSoftMemoryLimit(); 78 } 79 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 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 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 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 161 void DiscardableMemoryManager::PurgeAll() { 162 AutoLock lock(lock_); 163 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(Now(), 0); 164 } 165 166 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 173 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 180 size_t DiscardableMemoryManager::GetBytesAllocatedForTest() const { 181 AutoLock lock(lock_); 182 return bytes_allocated_; 183 } 184 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 200 DiscardableMemoryManager::PurgeUntilWithinBytesToKeepUnderModeratePressure() { 201 AutoLock lock(lock_); 202 203 PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired( 204 Now(), bytes_to_keep_under_moderate_pressure_); 205 } 206 207 bool DiscardableMemoryManager:: 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:: 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 248 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 258 TimeTicks DiscardableMemoryManager::Now() const { 259 return TimeTicks::Now(); 260 } 261 262 } // namespace internal 263 } // namespace base 264