blob: 6ede8a39cf8bbc54659468e7adb520103d2c7898 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/discardable_memory_manager.h"
#include "base/bind.h"
#include "base/containers/hash_tables.h"
#include "base/containers/mru_cache.h"
#include "base/debug/trace_event.h"
#include "base/synchronization/lock.h"
namespace base {
namespace internal {
namespace {
// This is admittedly pretty magical. It's approximately enough memory for four
// 2560x1600 images.
static const size_t kDefaultMemoryLimit = 64 * 1024 * 1024;
static const size_t kDefaultBytesToKeepUnderModeratePressure =
kDefaultMemoryLimit / 4;
} // namespace
DiscardableMemoryManager::DiscardableMemoryManager()
: allocations_(AllocationMap::NO_AUTO_EVICT),
bytes_allocated_(0),
memory_limit_(kDefaultMemoryLimit),
bytes_to_keep_under_moderate_pressure_(
kDefaultBytesToKeepUnderModeratePressure) {
BytesAllocatedChanged();
}
DiscardableMemoryManager::~DiscardableMemoryManager() {
DCHECK(allocations_.empty());
DCHECK_EQ(0u, bytes_allocated_);
}
void DiscardableMemoryManager::RegisterMemoryPressureListener() {
AutoLock lock(lock_);
DCHECK(base::MessageLoop::current());
DCHECK(!memory_pressure_listener_);
memory_pressure_listener_.reset(new MemoryPressureListener(base::Bind(
&DiscardableMemoryManager::OnMemoryPressure, Unretained(this))));
}
void DiscardableMemoryManager::UnregisterMemoryPressureListener() {
AutoLock lock(lock_);
DCHECK(memory_pressure_listener_);
memory_pressure_listener_.reset();
}
void DiscardableMemoryManager::SetMemoryLimit(size_t bytes) {
AutoLock lock(lock_);
memory_limit_ = bytes;
EnforcePolicyWithLockAcquired();
}
void DiscardableMemoryManager::SetBytesToKeepUnderModeratePressure(
size_t bytes) {
AutoLock lock(lock_);
bytes_to_keep_under_moderate_pressure_ = bytes;
}
void DiscardableMemoryManager::Register(Allocation* allocation, size_t bytes) {
AutoLock lock(lock_);
// A registered memory listener is currently required. This DCHECK can be
// moved or removed if we decide that it's useful to relax this condition.
// TODO(reveman): Enable this DCHECK when skia and blink are able to
// register memory pressure listeners. crbug.com/333907
// DCHECK(memory_pressure_listener_);
DCHECK(allocations_.Peek(allocation) == allocations_.end());
allocations_.Put(allocation, AllocationInfo(bytes));
}
void DiscardableMemoryManager::Unregister(Allocation* allocation) {
AutoLock lock(lock_);
AllocationMap::iterator it = allocations_.Peek(allocation);
DCHECK(it != allocations_.end());
const AllocationInfo& info = it->second;
if (info.purgable) {
size_t bytes_purgable = info.bytes;
DCHECK_LE(bytes_purgable, bytes_allocated_);
bytes_allocated_ -= bytes_purgable;
BytesAllocatedChanged();
}
allocations_.Erase(it);
}
bool DiscardableMemoryManager::AcquireLock(Allocation* allocation,
bool* purged) {
AutoLock lock(lock_);
// Note: |allocations_| is an MRU cache, and use of |Get| here updates that
// cache.
AllocationMap::iterator it = allocations_.Get(allocation);
DCHECK(it != allocations_.end());
AllocationInfo* info = &it->second;
if (!info->bytes)
return false;
size_t bytes_required = info->purgable ? 0u : info->bytes;
if (memory_limit_) {
size_t limit = 0;
if (bytes_required < memory_limit_)
limit = memory_limit_ - bytes_required;
PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit);
}
// Check for overflow.
if (std::numeric_limits<size_t>::max() - bytes_required < bytes_allocated_)
return false;
*purged = !allocation->AllocateAndAcquireLock();
info->purgable = false;
if (bytes_required) {
bytes_allocated_ += bytes_required;
BytesAllocatedChanged();
}
return true;
}
void DiscardableMemoryManager::ReleaseLock(Allocation* allocation) {
AutoLock lock(lock_);
// Note: |allocations_| is an MRU cache, and use of |Get| here updates that
// cache.
AllocationMap::iterator it = allocations_.Get(allocation);
DCHECK(it != allocations_.end());
AllocationInfo* info = &it->second;
allocation->ReleaseLock();
info->purgable = true;
EnforcePolicyWithLockAcquired();
}
void DiscardableMemoryManager::PurgeAll() {
AutoLock lock(lock_);
PurgeLRUWithLockAcquiredUntilUsageIsWithin(0);
}
bool DiscardableMemoryManager::IsRegisteredForTest(
Allocation* allocation) const {
AutoLock lock(lock_);
AllocationMap::const_iterator it = allocations_.Peek(allocation);
return it != allocations_.end();
}
bool DiscardableMemoryManager::CanBePurgedForTest(
Allocation* allocation) const {
AutoLock lock(lock_);
AllocationMap::const_iterator it = allocations_.Peek(allocation);
return it != allocations_.end() && it->second.purgable;
}
size_t DiscardableMemoryManager::GetBytesAllocatedForTest() const {
AutoLock lock(lock_);
return bytes_allocated_;
}
void DiscardableMemoryManager::OnMemoryPressure(
MemoryPressureListener::MemoryPressureLevel pressure_level) {
switch (pressure_level) {
case MemoryPressureListener::MEMORY_PRESSURE_MODERATE:
Purge();
return;
case MemoryPressureListener::MEMORY_PRESSURE_CRITICAL:
PurgeAll();
return;
}
NOTREACHED();
}
void DiscardableMemoryManager::Purge() {
AutoLock lock(lock_);
PurgeLRUWithLockAcquiredUntilUsageIsWithin(
bytes_to_keep_under_moderate_pressure_);
}
void DiscardableMemoryManager::PurgeLRUWithLockAcquiredUntilUsageIsWithin(
size_t limit) {
TRACE_EVENT1(
"base",
"DiscardableMemoryManager::PurgeLRUWithLockAcquiredUntilUsageIsWithin",
"limit",
limit);
lock_.AssertAcquired();
size_t bytes_allocated_before_purging = bytes_allocated_;
for (AllocationMap::reverse_iterator it = allocations_.rbegin();
it != allocations_.rend();
++it) {
Allocation* allocation = it->first;
AllocationInfo* info = &it->second;
if (bytes_allocated_ <= limit)
break;
if (!info->purgable)
continue;
size_t bytes_purgable = info->bytes;
DCHECK_LE(bytes_purgable, bytes_allocated_);
bytes_allocated_ -= bytes_purgable;
info->purgable = false;
allocation->Purge();
}
if (bytes_allocated_ != bytes_allocated_before_purging)
BytesAllocatedChanged();
}
void DiscardableMemoryManager::EnforcePolicyWithLockAcquired() {
PurgeLRUWithLockAcquiredUntilUsageIsWithin(memory_limit_);
}
void DiscardableMemoryManager::BytesAllocatedChanged() const {
TRACE_COUNTER_ID1("base", "DiscardableMemoryUsage", this, bytes_allocated_);
}
} // namespace internal
} // namespace base