Skip to content

Commit

Permalink
Do V8 GC ASAP if system memory is pressured
Browse files Browse the repository at this point in the history
Currently after receiving memory pressure notification, Chrome posts
asynchronous task to renderer main thread to trigger V8 GC. If renderer
main thread is busy, purging memory task will be postponed running,
which increases OOM risk.

The CL can send memory pressure notification to renderer main thread
synchronously and trigger V8 GC while V8 is in the middle of executing
JavaScript.

The CL depends on CL 1813963002

BUG=590975

Review-Url: https://codereview.chromium.org/1749073002
Cr-Commit-Position: refs/heads/master@{#391167}
  • Loading branch information
HongZheng authored and Commit bot committed May 3, 2016
1 parent ba37f81 commit f80422e
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 49 deletions.
80 changes: 58 additions & 22 deletions base/memory/memory_pressure_listener.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,82 @@ namespace base {

namespace {

// ObserverListThreadSafe is RefCountedThreadSafe, this traits is needed
// to ensure the LazyInstance will hold a reference to it.
struct LeakyLazyObserverListTraits :
base::internal::LeakyLazyInstanceTraits<
ObserverListThreadSafe<MemoryPressureListener> > {
static ObserverListThreadSafe<MemoryPressureListener>*
New(void* instance) {
ObserverListThreadSafe<MemoryPressureListener>* ret =
base::internal::LeakyLazyInstanceTraits<
ObserverListThreadSafe<MemoryPressureListener>>::New(instance);
// Leaky.
ret->AddRef();
return ret;
class MemoryPressureObserver {
public:
MemoryPressureObserver()
: async_observers_(new ObserverListThreadSafe<MemoryPressureListener>),
sync_observers_(new ObserverList<MemoryPressureListener>) {
}

void AddObserver(MemoryPressureListener* listener, bool sync) {
async_observers_->AddObserver(listener);
if (sync) {
AutoLock lock(sync_observers_lock_);
sync_observers_->AddObserver(listener);
}
}

void RemoveObserver(MemoryPressureListener* listener) {
async_observers_->RemoveObserver(listener);
AutoLock lock(sync_observers_lock_);
sync_observers_->RemoveObserver(listener);
}

void Notify(MemoryPressureListener::MemoryPressureLevel
memory_pressure_level) {
async_observers_->Notify(FROM_HERE,
&MemoryPressureListener::Notify, memory_pressure_level);
AutoLock lock(sync_observers_lock_);
FOR_EACH_OBSERVER(MemoryPressureListener, *sync_observers_,
MemoryPressureListener::SyncNotify(memory_pressure_level));
}

private:
scoped_refptr<ObserverListThreadSafe<MemoryPressureListener>>
async_observers_;
ObserverList<MemoryPressureListener>* sync_observers_;
Lock sync_observers_lock_;

DISALLOW_COPY_AND_ASSIGN(MemoryPressureObserver);
};

LazyInstance<
ObserverListThreadSafe<MemoryPressureListener>,
LeakyLazyObserverListTraits> g_observers = LAZY_INSTANCE_INITIALIZER;
LazyInstance<MemoryPressureObserver>::Leaky g_observer =
LAZY_INSTANCE_INITIALIZER;

// All memory pressure notifications within this process will be suppressed if
// this variable is set to 1.
subtle::Atomic32 g_notifications_suppressed = 0;

} // namespace

MemoryPressureListener::MemoryPressureListener(
const MemoryPressureListener::MemoryPressureCallback& callback)
: callback_(callback) {
g_observers.Get().AddObserver(this);
g_observer.Get().AddObserver(this, false);
}

MemoryPressureListener::MemoryPressureListener(
const MemoryPressureListener::MemoryPressureCallback& callback,
const MemoryPressureListener::SyncMemoryPressureCallback&
sync_memory_pressure_callback)
: callback_(callback),
sync_memory_pressure_callback_(sync_memory_pressure_callback) {
g_observer.Get().AddObserver(this, true);
}

MemoryPressureListener::~MemoryPressureListener() {
g_observers.Get().RemoveObserver(this);
g_observer.Get().RemoveObserver(this);
}

void MemoryPressureListener::Notify(MemoryPressureLevel memory_pressure_level) {
callback_.Run(memory_pressure_level);
}

void MemoryPressureListener::SyncNotify(
MemoryPressureLevel memory_pressure_level) {
if (!sync_memory_pressure_callback_.is_null()) {
sync_memory_pressure_callback_.Run(memory_pressure_level);
}
}

// static
void MemoryPressureListener::NotifyMemoryPressure(
MemoryPressureLevel memory_pressure_level) {
Expand Down Expand Up @@ -86,8 +122,8 @@ void MemoryPressureListener::SimulatePressureNotification(
void MemoryPressureListener::DoNotifyMemoryPressure(
MemoryPressureLevel memory_pressure_level) {
DCHECK_NE(memory_pressure_level, MEMORY_PRESSURE_LEVEL_NONE);
g_observers.Get().Notify(FROM_HERE, &MemoryPressureListener::Notify,
memory_pressure_level);

g_observer.Get().Notify(memory_pressure_level);
}

} // namespace base
17 changes: 12 additions & 5 deletions base/memory/memory_pressure_listener.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,27 @@ class BASE_EXPORT MemoryPressureListener {
// No problems, there is enough memory to use. This event is not sent via
// callback, but the enum is used in other places to find out the current
// state of the system.
MEMORY_PRESSURE_LEVEL_NONE = -1,
MEMORY_PRESSURE_LEVEL_NONE,

// Modules are advised to free buffers that are cheap to re-allocate and not
// immediately needed.
MEMORY_PRESSURE_LEVEL_MODERATE = 0,
MEMORY_PRESSURE_LEVEL_MODERATE,

// At this level, modules are advised to free all possible memory. The
// alternative is to be killed by the system, which means all memory will
// have to be re-created, plus the cost of a cold start.
MEMORY_PRESSURE_LEVEL_CRITICAL = 2,
MEMORY_PRESSURE_LEVEL_CRITICAL,
};

typedef base::Callback<void(MemoryPressureLevel)> MemoryPressureCallback;
typedef Callback<void(MemoryPressureLevel)> MemoryPressureCallback;
typedef Callback<void(MemoryPressureLevel)> SyncMemoryPressureCallback;

explicit MemoryPressureListener(
const MemoryPressureCallback& memory_pressure_callback);
MemoryPressureListener(
const MemoryPressureCallback& memory_pressure_callback,
const SyncMemoryPressureCallback& sync_memory_pressure_callback);

~MemoryPressureListener();

// Intended for use by the platform specific implementation.
Expand All @@ -80,12 +85,14 @@ class BASE_EXPORT MemoryPressureListener {
static void SimulatePressureNotification(
MemoryPressureLevel memory_pressure_level);

private:
void Notify(MemoryPressureLevel memory_pressure_level);
void SyncNotify(MemoryPressureLevel memory_pressure_level);

private:
static void DoNotifyMemoryPressure(MemoryPressureLevel memory_pressure_level);

MemoryPressureCallback callback_;
SyncMemoryPressureCallback sync_memory_pressure_callback_;

DISALLOW_COPY_AND_ASSIGN(MemoryPressureListener);
};
Expand Down
57 changes: 35 additions & 22 deletions content/renderer/render_thread_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@
#include "third_party/skia/include/core/SkGraphics.h"
#include "ui/base/layout.h"
#include "ui/base/ui_base_switches.h"
#include "v8/include/v8.h"

#if defined(OS_ANDROID)
#include <cpu-features.h>
Expand Down Expand Up @@ -241,6 +240,17 @@ const size_t kImageCacheSingleAllocationByteLimit = 64 * 1024 * 1024;
base::LazyInstance<base::ThreadLocalPointer<RenderThreadImpl> >
lazy_tls = LAZY_INSTANCE_INITIALIZER;

// v8::MemoryPressureLevel should correspond to base::MemoryPressureListener.
static_assert(static_cast<v8::MemoryPressureLevel>(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) ==
v8::MemoryPressureLevel::kNone, "none level not align");
static_assert(static_cast<v8::MemoryPressureLevel>(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE) ==
v8::MemoryPressureLevel::kModerate, "moderate level not align");
static_assert(static_cast<v8::MemoryPressureLevel>(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) ==
v8::MemoryPressureLevel::kCritical, "critical level not align");

class WebThreadForCompositor : public WebThreadImplForWorkerScheduler {
public:
explicit WebThreadForCompositor(base::Thread::Options options)
Expand Down Expand Up @@ -302,13 +312,6 @@ void NotifyTimezoneChangeOnThisThread() {
v8::Date::DateTimeConfigurationChangeNotification(isolate);
}

void LowMemoryNotificationOnThisThread() {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
if (!isolate)
return;
isolate->LowMemoryNotification();
}

class RenderFrameSetupImpl : public mojom::RenderFrameSetup {
public:
explicit RenderFrameSetupImpl(
Expand Down Expand Up @@ -774,7 +777,9 @@ void RenderThreadImpl::Init(
#endif

memory_pressure_listener_.reset(new base::MemoryPressureListener(
base::Bind(&RenderThreadImpl::OnMemoryPressure, base::Unretained(this))));
base::Bind(&RenderThreadImpl::OnMemoryPressure, base::Unretained(this)),
base::Bind(&RenderThreadImpl::OnSyncMemoryPressure,
base::Unretained(this))));

int num_raster_threads = 0;
std::string string_value =
Expand Down Expand Up @@ -1864,26 +1869,14 @@ void RenderThreadImpl::OnCreateNewSharedWorker(

void RenderThreadImpl::OnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
TRACE_EVENT0("memory","RenderThreadImpl::OnMemoryPressure");
ReleaseFreeMemory();

// Do not call into blink if it is not initialized.
if (blink_platform_impl_) {
blink::WebMemoryPressureListener::onMemoryPressure(
static_cast<blink::WebMemoryPressureLevel>(memory_pressure_level));

if (blink::mainThreadIsolate()) {
// Trigger full v8 garbage collection on memory pressure notifications.
// This will potentially hang the renderer for a long time, however, when
// we receive a memory pressure notification, we might be about to be
// killed. Because of the janky hang don't do this to foreground
// renderers.
if (RendererIsHidden()) {
blink::mainThreadIsolate()->LowMemoryNotification();
RenderThread::Get()->PostTaskToAllWebWorkers(
base::Bind(&LowMemoryNotificationOnThisThread));
}
}

if (memory_pressure_level ==
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
// Purge Skia font cache, by setting it to 0 and then again to the
Expand Down Expand Up @@ -2043,4 +2036,24 @@ void RenderThreadImpl::PendingRenderFrameConnect::OnConnectionError() {
DCHECK_EQ(1u, erased);
}

void RenderThreadImpl::OnSyncMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
if (!blink::mainThreadIsolate())
return;

v8::MemoryPressureLevel v8_memory_pressure_level =
static_cast<v8::MemoryPressureLevel>(memory_pressure_level);

// In order to reduce performance impact, translate critical level to
// moderate level for foregroud renderer.
if (!RendererIsHidden() &&
v8_memory_pressure_level == v8::MemoryPressureLevel::kCritical)
v8_memory_pressure_level = v8::MemoryPressureLevel::kModerate;

blink::mainThreadIsolate()->MemoryPressureNotification(
v8_memory_pressure_level);
blink::MemoryPressureNotificationToWorkerThreadIsolates(
v8_memory_pressure_level);
}

} // namespace content
3 changes: 3 additions & 0 deletions content/renderer/render_thread_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,9 @@ class CONTENT_EXPORT RenderThreadImpl

void ReleaseFreeMemory();

void OnSyncMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level);

// These objects live solely on the render thread.
std::unique_ptr<AppCacheDispatcher> appcache_dispatcher_;
std::unique_ptr<DomStorageDispatcher> dom_storage_dispatcher_;
Expand Down
39 changes: 39 additions & 0 deletions third_party/WebKit/Source/core/workers/WorkerBackingThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,34 @@

namespace blink {

#define DEFINE_STATIC_LOCAL_WITH_LOCK(type, name, arguments) \
ASSERT(isolatesMutex().locked()); \
static type& name = *new type arguments

static Mutex& isolatesMutex()
{
DEFINE_THREAD_SAFE_STATIC_LOCAL(Mutex, mutex, new Mutex);
return mutex;
}

static HashSet<v8::Isolate*>& isolates()
{
DEFINE_STATIC_LOCAL_WITH_LOCK(HashSet<v8::Isolate*>, isolates, ());
return isolates;
}

static void addWorkerIsolate(v8::Isolate* isolate)
{
MutexLocker lock(isolatesMutex());
isolates().add(isolate);
}

static void removeWorkerIsolate(v8::Isolate* isolate)
{
MutexLocker lock(isolatesMutex());
isolates().remove(isolate);
}

WorkerBackingThread::WorkerBackingThread(const char* name, bool shouldCallGCOnShutdown)
: m_backingThread(WebThreadSupportingGC::create(name))
, m_isOwningThread(true)
Expand Down Expand Up @@ -63,6 +91,7 @@ void WorkerBackingThread::initialize()
{
DCHECK(!m_isolate);
m_isolate = V8PerIsolateData::initialize();
addWorkerIsolate(m_isolate);
V8Initializer::initializeWorker(m_isolate);
m_backingThread->initialize();

Expand All @@ -89,7 +118,17 @@ void WorkerBackingThread::shutdown()
m_backingThread->shutdown();

V8PerIsolateData::destroy(m_isolate);
removeWorkerIsolate(m_isolate);
m_isolate = nullptr;
}

// static
void WorkerBackingThread::MemoryPressureNotificationToWorkerThreadIsolates(
v8::MemoryPressureLevel level)
{
MutexLocker lock(isolatesMutex());
for (v8::Isolate* isolate : isolates())
isolate->MemoryPressureNotification(level);
}

} // namespace blink
3 changes: 3 additions & 0 deletions third_party/WebKit/Source/core/workers/WorkerBackingThread.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ class CORE_EXPORT WorkerBackingThread final {

v8::Isolate* isolate() { return m_isolate; }

static void MemoryPressureNotificationToWorkerThreadIsolates(
v8::MemoryPressureLevel);

private:
WorkerBackingThread(const char* name, bool shouldCallGCOnShutdown);
WorkerBackingThread(WebThread*, bool shouldCallGCOnSHutdown);
Expand Down
8 changes: 8 additions & 0 deletions third_party/WebKit/Source/web/WebKit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "bindings/core/v8/V8Initializer.h"
#include "core/animation/AnimationClock.h"
#include "core/page/Page.h"
#include "core/workers/WorkerBackingThread.h"
#include "gin/public/v8_platform.h"
#include "modules/ModulesInitializer.h"
#include "platform/LayoutTestSupport.h"
Expand Down Expand Up @@ -176,4 +177,11 @@ void decommitFreeableMemory()
WTF::Partitions::decommitFreeableMemory();
}

void MemoryPressureNotificationToWorkerThreadIsolates(
v8::MemoryPressureLevel level)
{
WorkerBackingThread::
MemoryPressureNotificationToWorkerThreadIsolates(level);
}

} // namespace blink
5 changes: 5 additions & 0 deletions third_party/WebKit/public/web/WebKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

namespace v8 {
class Isolate;
enum class MemoryPressureLevel;
}

namespace blink {
Expand Down Expand Up @@ -84,6 +85,10 @@ BLINK_EXPORT void resetPluginCache(bool reloadPages = false);
// performance and memory usage.
BLINK_EXPORT void decommitFreeableMemory();

// Send memory pressure notification to worker thread isolate.
BLINK_EXPORT void MemoryPressureNotificationToWorkerThreadIsolates(
v8::MemoryPressureLevel);

} // namespace blink

#endif

0 comments on commit f80422e

Please sign in to comment.