Home | History | Annotate | Download | only in memory
      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.
      4 
      5 #include "base/memory/discardable_memory_provider.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/trace_event.h"
     11 #include "base/synchronization/lock.h"
     12 #include "base/sys_info.h"
     13 
     14 namespace base {
     15 namespace internal {
     16 
     17 namespace {
     18 
     19 // This is admittedly pretty magical. It's approximately enough memory for two
     20 // 2560x1600 images.
     21 static const size_t kDefaultDiscardableMemoryLimit = 32 * 1024 * 1024;
     22 static const size_t kDefaultBytesToReclaimUnderModeratePressure =
     23     kDefaultDiscardableMemoryLimit / 2;
     24 
     25 }  // namespace
     26 
     27 DiscardableMemoryProvider::DiscardableMemoryProvider()
     28     : allocations_(AllocationMap::NO_AUTO_EVICT),
     29       bytes_allocated_(0),
     30       discardable_memory_limit_(kDefaultDiscardableMemoryLimit),
     31       bytes_to_reclaim_under_moderate_pressure_(
     32           kDefaultBytesToReclaimUnderModeratePressure),
     33       memory_pressure_listener_(
     34           base::Bind(&DiscardableMemoryProvider::NotifyMemoryPressure,
     35                      Unretained(this))) {
     36 }
     37 
     38 DiscardableMemoryProvider::~DiscardableMemoryProvider() {
     39   DCHECK(allocations_.empty());
     40   DCHECK_EQ(0u, bytes_allocated_);
     41 }
     42 
     43 void DiscardableMemoryProvider::NotifyMemoryPressure(
     44     MemoryPressureListener::MemoryPressureLevel pressure_level) {
     45   switch (pressure_level) {
     46     case MemoryPressureListener::MEMORY_PRESSURE_MODERATE:
     47       Purge();
     48       return;
     49     case MemoryPressureListener::MEMORY_PRESSURE_CRITICAL:
     50       PurgeAll();
     51       return;
     52   }
     53 
     54   NOTREACHED();
     55 }
     56 
     57 void DiscardableMemoryProvider::SetDiscardableMemoryLimit(size_t bytes) {
     58   AutoLock lock(lock_);
     59   discardable_memory_limit_ = bytes;
     60   EnforcePolicyWithLockAcquired();
     61 }
     62 
     63 void DiscardableMemoryProvider::SetBytesToReclaimUnderModeratePressure(
     64     size_t bytes) {
     65   AutoLock lock(lock_);
     66   bytes_to_reclaim_under_moderate_pressure_ = bytes;
     67 }
     68 
     69 void DiscardableMemoryProvider::Register(
     70     const DiscardableMemory* discardable, size_t bytes) {
     71   AutoLock lock(lock_);
     72   DCHECK(allocations_.Peek(discardable) == allocations_.end());
     73   allocations_.Put(discardable, Allocation(bytes));
     74 }
     75 
     76 void DiscardableMemoryProvider::Unregister(
     77     const DiscardableMemory* discardable) {
     78   AutoLock lock(lock_);
     79   AllocationMap::iterator it = allocations_.Peek(discardable);
     80   if (it == allocations_.end())
     81     return;
     82 
     83   if (it->second.memory) {
     84     size_t bytes = it->second.bytes;
     85     DCHECK_LE(bytes, bytes_allocated_);
     86     bytes_allocated_ -= bytes;
     87     free(it->second.memory);
     88   }
     89   allocations_.Erase(it);
     90 }
     91 
     92 scoped_ptr<uint8, FreeDeleter> DiscardableMemoryProvider::Acquire(
     93     const DiscardableMemory* discardable,
     94     bool* purged) {
     95   AutoLock lock(lock_);
     96   // NB: |allocations_| is an MRU cache, and use of |Get| here updates that
     97   // cache.
     98   AllocationMap::iterator it = allocations_.Get(discardable);
     99   CHECK(it != allocations_.end());
    100 
    101   if (it->second.memory) {
    102     scoped_ptr<uint8, FreeDeleter> memory(it->second.memory);
    103     it->second.memory = NULL;
    104     *purged = false;
    105     return memory.Pass();
    106   }
    107 
    108   size_t bytes = it->second.bytes;
    109   if (!bytes)
    110     return scoped_ptr<uint8, FreeDeleter>();
    111 
    112   if (discardable_memory_limit_) {
    113     size_t limit = 0;
    114     if (bytes < discardable_memory_limit_)
    115       limit = discardable_memory_limit_ - bytes;
    116 
    117     PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit);
    118   }
    119 
    120   // Check for overflow.
    121   if (std::numeric_limits<size_t>::max() - bytes < bytes_allocated_)
    122     return scoped_ptr<uint8, FreeDeleter>();
    123 
    124   scoped_ptr<uint8, FreeDeleter> memory(static_cast<uint8*>(malloc(bytes)));
    125   if (!memory)
    126     return scoped_ptr<uint8, FreeDeleter>();
    127 
    128   bytes_allocated_ += bytes;
    129   *purged = true;
    130   return memory.Pass();
    131 }
    132 
    133 void DiscardableMemoryProvider::Release(
    134     const DiscardableMemory* discardable,
    135     scoped_ptr<uint8, FreeDeleter> memory) {
    136   AutoLock lock(lock_);
    137   // NB: |allocations_| is an MRU cache, and use of |Get| here updates that
    138   // cache.
    139   AllocationMap::iterator it = allocations_.Get(discardable);
    140   CHECK(it != allocations_.end());
    141 
    142   DCHECK(!it->second.memory);
    143   it->second.memory = memory.release();
    144 
    145   EnforcePolicyWithLockAcquired();
    146 }
    147 
    148 void DiscardableMemoryProvider::PurgeAll() {
    149   AutoLock lock(lock_);
    150   PurgeLRUWithLockAcquiredUntilUsageIsWithin(0);
    151 }
    152 
    153 bool DiscardableMemoryProvider::IsRegisteredForTest(
    154     const DiscardableMemory* discardable) const {
    155   AutoLock lock(lock_);
    156   AllocationMap::const_iterator it = allocations_.Peek(discardable);
    157   return it != allocations_.end();
    158 }
    159 
    160 bool DiscardableMemoryProvider::CanBePurgedForTest(
    161     const DiscardableMemory* discardable) const {
    162   AutoLock lock(lock_);
    163   AllocationMap::const_iterator it = allocations_.Peek(discardable);
    164   return it != allocations_.end() && it->second.memory;
    165 }
    166 
    167 size_t DiscardableMemoryProvider::GetBytesAllocatedForTest() const {
    168   AutoLock lock(lock_);
    169   return bytes_allocated_;
    170 }
    171 
    172 void DiscardableMemoryProvider::Purge() {
    173   AutoLock lock(lock_);
    174 
    175   if (bytes_to_reclaim_under_moderate_pressure_ == 0)
    176     return;
    177 
    178   size_t limit = 0;
    179   if (bytes_to_reclaim_under_moderate_pressure_ < bytes_allocated_)
    180     limit = bytes_allocated_ - bytes_to_reclaim_under_moderate_pressure_;
    181 
    182   PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit);
    183 }
    184 
    185 void DiscardableMemoryProvider::PurgeLRUWithLockAcquiredUntilUsageIsWithin(
    186     size_t limit) {
    187   TRACE_EVENT1(
    188       "base",
    189       "DiscardableMemoryProvider::PurgeLRUWithLockAcquiredUntilUsageIsWithin",
    190       "limit", limit);
    191 
    192   lock_.AssertAcquired();
    193 
    194   for (AllocationMap::reverse_iterator it = allocations_.rbegin();
    195        it != allocations_.rend();
    196        ++it) {
    197     if (bytes_allocated_ <= limit)
    198       break;
    199     if (!it->second.memory)
    200       continue;
    201 
    202     size_t bytes = it->second.bytes;
    203     DCHECK_LE(bytes, bytes_allocated_);
    204     bytes_allocated_ -= bytes;
    205     free(it->second.memory);
    206     it->second.memory = NULL;
    207   }
    208 }
    209 
    210 void DiscardableMemoryProvider::EnforcePolicyWithLockAcquired() {
    211   PurgeLRUWithLockAcquiredUntilUsageIsWithin(discardable_memory_limit_);
    212 }
    213 
    214 }  // namespace internal
    215 }  // namespace base
    216