2 * Copyright (C) 2011-2015 Apple Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #import "MemoryPressureHandler.h"
29 #import "DispatchSPI.h"
30 #import "IOSurfacePool.h"
31 #import "GCController.h"
32 #import "JSDOMWindow.h"
33 #import "JSDOMWindowBase.h"
36 #import "WebCoreSystemInterface.h"
37 #import <JavaScriptCore/IncrementalSweeper.h>
39 #import <mach/task_info.h>
40 #import <malloc/malloc.h>
42 #import <wtf/CurrentTime.h>
45 #import "SystemMemory.h"
46 #import "WebCoreThread.h"
49 extern "C" void cache_simulate_memory_warning_event(uint64_t);
50 extern "C" void _sqlite3_purgeEligiblePagerCacheMemory(void);
54 void MemoryPressureHandler::platformReleaseMemory(bool critical)
57 ReliefLogger log("Purging SQLite caches");
58 _sqlite3_purgeEligiblePagerCacheMemory();
62 ReliefLogger log("Drain LayerPools");
63 for (auto& pool : LayerPool::allLayerPools())
68 ReliefLogger log("Drain IOSurfacePool");
69 IOSurfacePool::sharedPool().discardAllSurfaces();
73 #if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
74 if (critical && !isUnderMemoryPressure()) {
75 // libcache listens to OS memory notifications, but for process suspension
76 // or memory pressure simulation, we need to prod it manually:
77 ReliefLogger log("Purging libcache caches");
78 cache_simulate_memory_warning_event(DISPATCH_MEMORYPRESSURE_CRITICAL);
81 UNUSED_PARAM(critical);
85 if (isUnderMemoryPressure()) {
86 gcController().garbageCollectSoon();
88 // If we're not under memory pressure, that means we're here due to impending process suspension.
89 // Do a full GC since this is our last chance to run any code.
90 ReliefLogger log("Collecting JavaScript garbage");
91 gcController().garbageCollectNow();
94 // Do a full sweep of collected objects.
96 ReliefLogger log("Full JavaScript garbage sweep");
97 JSC::JSLockHolder lock(JSDOMWindow::commonVM());
98 JSDOMWindow::commonVM().heap.sweeper()->fullSweep();
103 static dispatch_source_t _cache_event_source = 0;
104 static dispatch_source_t _timer_event_source = 0;
105 static int _notifyToken;
107 // Disable memory event reception for a minimum of s_minimumHoldOffTime
108 // seconds after receiving an event. Don't let events fire any sooner than
109 // s_holdOffMultiplier times the last cleanup processing time. Effectively
110 // this is 1 / s_holdOffMultiplier percent of the time.
111 // These value seems reasonable and testing verifies that it throttles frequent
112 // low memory events, greatly reducing CPU usage.
113 static const unsigned s_minimumHoldOffTime = 5;
115 static const unsigned s_holdOffMultiplier = 20;
118 void MemoryPressureHandler::install()
120 if (m_installed || _timer_event_source)
123 dispatch_async(dispatch_get_main_queue(), ^{
124 #if PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
125 _cache_event_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MEMORYSTATUS, 0, DISPATCH_MEMORYSTATUS_PRESSURE_NORMAL | DISPATCH_MEMORYSTATUS_PRESSURE_WARN | DISPATCH_MEMORYSTATUS_PRESSURE_CRITICAL, dispatch_get_main_queue());
127 _cache_event_source = wkCreateMemoryStatusPressureCriticalDispatchOnMainQueue();
129 _cache_event_source = wkCreateVMPressureDispatchOnMainQueue();
131 if (_cache_event_source) {
132 dispatch_set_context(_cache_event_source, this);
133 dispatch_source_set_event_handler(_cache_event_source, ^{
134 bool critical = true;
135 #if PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
136 unsigned long status = dispatch_source_get_data(_cache_event_source);
137 critical = status == DISPATCH_MEMORYPRESSURE_CRITICAL;
138 auto& memoryPressureHandler = MemoryPressureHandler::singleton();
139 bool wasCritical = memoryPressureHandler.isUnderMemoryPressure();
140 memoryPressureHandler.setUnderMemoryPressure(critical);
141 if (status == DISPATCH_MEMORYSTATUS_PRESSURE_NORMAL) {
142 if (ReliefLogger::loggingEnabled())
143 NSLog(@"System is no longer under (%s) memory pressure.", wasCritical ? "critical" : "non-critical");
147 if (ReliefLogger::loggingEnabled())
148 NSLog(@"Got memory pressure notification (%s)", critical ? "critical" : "non-critical");
150 MemoryPressureHandler::singleton().respondToMemoryPressure(critical);
152 dispatch_resume(_cache_event_source);
156 // Allow simulation of memory pressure with "notifyutil -p org.WebKit.lowMemory"
157 notify_register_dispatch("org.WebKit.lowMemory", &_notifyToken, dispatch_get_main_queue(), ^(int) {
158 MemoryPressureHandler::singleton().respondToMemoryPressure(true);
160 // We only do a synchronous GC when *simulating* memory pressure.
161 // This gives us a more consistent picture of live objects at the end of testing.
162 gcController().garbageCollectNow();
164 WTF::releaseFastMallocFreeMemory();
166 malloc_zone_pressure_relief(nullptr, 0);
172 void MemoryPressureHandler::uninstall()
177 dispatch_async(dispatch_get_main_queue(), ^{
178 if (_cache_event_source) {
179 dispatch_source_cancel(_cache_event_source);
180 dispatch_release(_cache_event_source);
181 _cache_event_source = 0;
184 if (_timer_event_source) {
185 dispatch_source_cancel(_timer_event_source);
186 dispatch_release(_timer_event_source);
187 _timer_event_source = 0;
193 notify_cancel(_notifyToken);
196 void MemoryPressureHandler::holdOff(unsigned seconds)
198 dispatch_async(dispatch_get_main_queue(), ^{
199 _timer_event_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
200 if (_timer_event_source) {
201 dispatch_set_context(_timer_event_source, this);
202 dispatch_source_set_timer(_timer_event_source, dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 1 * s_minimumHoldOffTime);
203 dispatch_source_set_event_handler(_timer_event_source, ^{
204 if (_timer_event_source) {
205 dispatch_source_cancel(_timer_event_source);
206 dispatch_release(_timer_event_source);
207 _timer_event_source = 0;
209 MemoryPressureHandler::singleton().install();
211 dispatch_resume(_timer_event_source);
216 void MemoryPressureHandler::respondToMemoryPressure(bool critical)
220 double startTime = monotonicallyIncreasingTime();
223 m_lowMemoryHandler(critical);
226 unsigned holdOffTime = (monotonicallyIncreasingTime() - startTime) * s_holdOffMultiplier;
227 holdOff(std::max(holdOffTime, s_minimumHoldOffTime));
231 size_t MemoryPressureHandler::ReliefLogger::platformMemoryUsage()
233 // Flush free memory back to the OS before every measurement.
234 // Note that this code only runs when detailed pressure relief logging is enabled.
235 WTF::releaseFastMallocFreeMemory();
236 malloc_zone_pressure_relief(nullptr, 0);
238 task_vm_info_data_t vmInfo;
239 mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
240 kern_return_t err = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);
241 if (err != KERN_SUCCESS)
242 return static_cast<size_t>(-1);
244 return static_cast<size_t>(vmInfo.internal);
247 void MemoryPressureHandler::ReliefLogger::platformLog()
249 size_t currentMemory = platformMemoryUsage();
250 if (currentMemory == static_cast<size_t>(-1) || m_initialMemory == static_cast<size_t>(-1)) {
251 NSLog(@"%s (Unable to get dirty memory information for process)\n", m_logString);
255 ssize_t memoryDiff = currentMemory - m_initialMemory;
257 NSLog(@"Pressure relief: %s: -dirty %ld bytes (from %ld to %ld)\n", m_logString, (memoryDiff * -1), m_initialMemory, currentMemory);
258 else if (memoryDiff > 0)
259 NSLog(@"Pressure relief: %s: +dirty %ld bytes (from %ld to %ld)\n", m_logString, memoryDiff, m_initialMemory, currentMemory);
261 NSLog(@"Pressure relief: %s: =dirty (at %ld bytes)\n", m_logString, currentMemory);
265 static void respondToMemoryPressureCallback(CFRunLoopObserverRef observer, CFRunLoopActivity /*activity*/, void* /*info*/)
267 MemoryPressureHandler::singleton().respondToMemoryPressureIfNeeded();
268 CFRunLoopObserverInvalidate(observer);
272 void MemoryPressureHandler::installMemoryReleaseBlock(void (^releaseMemoryBlock)(), bool clearPressureOnMemoryRelease)
276 m_releaseMemoryBlock = Block_copy(releaseMemoryBlock);
277 m_clearPressureOnMemoryRelease = clearPressureOnMemoryRelease;
281 void MemoryPressureHandler::setReceivedMemoryPressure(MemoryPressureReason reason)
283 m_underMemoryPressure = true;
286 MutexLocker locker(m_observerMutex);
288 m_observer = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting | kCFRunLoopExit, NO /* don't repeat */,
289 0, WebCore::respondToMemoryPressureCallback, NULL);
290 CFRunLoopAddObserver(WebThreadRunLoop(), m_observer, kCFRunLoopCommonModes);
291 CFRunLoopWakeUp(WebThreadRunLoop());
293 m_memoryPressureReason |= reason;
297 void MemoryPressureHandler::clearMemoryPressure()
299 m_underMemoryPressure = false;
302 MutexLocker locker(m_observerMutex);
303 m_memoryPressureReason = MemoryPressureReasonNone;
307 bool MemoryPressureHandler::shouldWaitForMemoryClearMessage()
309 MutexLocker locker(m_observerMutex);
310 return m_memoryPressureReason & MemoryPressureReasonVMStatus;
313 void MemoryPressureHandler::respondToMemoryPressureIfNeeded()
315 ASSERT(WebThreadIsLockedOrDisabled());
318 MutexLocker locker(m_observerMutex);
322 if (isUnderMemoryPressure()) {
323 ASSERT(m_releaseMemoryBlock);
324 LOG(MemoryPressure, "Handle memory pressure at %s", __PRETTY_FUNCTION__);
325 m_releaseMemoryBlock();
326 if (m_clearPressureOnMemoryRelease)
327 clearMemoryPressure();
333 } // namespace WebCore