上篇文章介绍了WKWebView 线程终止的原因——之 OOM 的控制逻辑,那 iOS 的最大可用内存到底是多少呢?我们可不可以将 WebKit 中的计算逻辑拿出来运行一下呢?
最终的实现过程可以查看 GitHub 上的 WKWebViewMemory。
抽离 WebKit 的计算方法
我们可以尝试将WKWebView 线程终止的原因——之 OOM 的控制逻辑中找的的对应方法,放到一个 app 程序当中,来获取对应的数值。
整体的方法整理在如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116#include "MemoryPressure.hpp" #include <iostream> #include <mutex> #include <array> #include <mutex> #import <algorithm> #import <dispatch/dispatch.h> #import <mach/host_info.h> #import <mach/mach.h> #import <mach/mach_error.h> #import <math.h> #define MEMORYSTATUS_CMD_GET_MEMLIMIT_PROPERTIES 8 static constexpr size_t kB = 1024; static constexpr size_t MB = kB * kB; static constexpr size_t GB = kB * kB * kB; static constexpr size_t availableMemoryGuess = 512 * MB; #if __has_include(<System/sys/kern_memorystatus.h>) extern "C" { #include <System/sys/kern_memorystatus.h> } #else extern "C" { using namespace std; typedef struct memorystatus_memlimit_properties { int32_t memlimit_active; /* jetsam memory limit (in MB) when process is active */ uint32_t memlimit_active_attr; int32_t memlimit_inactive; /* jetsam memory limit (in MB) when process is inactive */ uint32_t memlimit_inactive_attr; } memorystatus_memlimit_properties_t; #define MEMORYSTATUS_CMD_GET_MEMLIMIT_PROPERTIES 8 #define MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE 18 #define MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE 19 } #endif // __has_include(<System/sys/kern_memorystatus.h>) extern "C" { int memorystatus_control(uint32_t command, int32_t pid, uint32_t flags, void *buffer, size_t buffersize); } size_t MemoryPressure::jetsamLimit() { memorystatus_memlimit_properties_t properties; pid_t pid = getpid(); if (memorystatus_control(MEMORYSTATUS_CMD_GET_MEMLIMIT_PROPERTIES, pid, 0, &properties, sizeof(properties))) return 840 * MB; if (properties.memlimit_active < 0) return std::numeric_limits<size_t>::max(); return static_cast<size_t>(properties.memlimit_active) * MB; } size_t MemoryPressure::memorySizeAccordingToKernel() { host_basic_info_data_t hostInfo; mach_port_t host = mach_host_self(); mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; kern_return_t r = host_info(host, HOST_BASIC_INFO, (host_info_t)&hostInfo, &count); mach_port_deallocate(mach_task_self(), host); if (r != KERN_SUCCESS) return availableMemoryGuess; if (hostInfo.max_mem > std::numeric_limits<size_t>::max()) return std::numeric_limits<size_t>::max(); return static_cast<size_t>(hostInfo.max_mem); } size_t MemoryPressure::computeAvailableMemory() { size_t memorySize = memorySizeAccordingToKernel(); size_t sizeJetsamLimit = jetsamLimit(); cout << "jetsamLimit:" << sizeJetsamLimit / 1024 / 1024 << "MBn"; cout << "memorySize:" << memorySize / 1024 / 1024 << "MBn"; size_t sizeAccordingToKernel = std::min(memorySize, sizeJetsamLimit); size_t multiple = 128 * MB; // Round up the memory size to a multiple of 128MB because max_mem may not be exactly 512MB // (for example) and we have code that depends on those boundaries. sizeAccordingToKernel = ((sizeAccordingToKernel + multiple - 1) / multiple) * multiple; cout << "sizeAccordingToKernel:" << sizeAccordingToKernel / 1024 / 1024 << "MBn"; return sizeAccordingToKernel; } size_t MemoryPressure::availableMemory() { static size_t availableMemory; static std::once_flag onceFlag; std::call_once(onceFlag, [this] { availableMemory = computeAvailableMemory(); }); return availableMemory; } size_t MemoryPressure::computeRAMSize() { return availableMemory(); } size_t MemoryPressure::ramSize() { static size_t ramSize; static std::once_flag onceFlag; std::call_once(onceFlag, [this] { ramSize = computeRAMSize(); }); return ramSize; } size_t MemoryPressure::thresholdForMemoryKillOfActiveProcess(unsigned tabCount) { size_t ramSizeV = ramSize(); cout << "ramSize:" << ramSizeV / 1024 / 1024 << "MBn"; size_t baseThreshold = ramSizeV > 16 * GB ? 15 * GB : 7 * GB; return baseThreshold + tabCount * GB; } size_t MemoryPressure::thresholdForMemoryKillOfInactiveProcess(unsigned tabCount) { //#if CPU(X86_64) || CPU(ARM64) size_t baseThreshold = 3 * GB + tabCount * GB; //#else // size_t baseThreshold = tabCount > 1 ? 3 * GB : 2 * GB; //#endif return std::min(baseThreshold, static_cast<size_t>(ramSize() * 0.9)); }
上面方法中的具体作用,可以查看WKWebView 线程终止的原因——之 OOM 的控制逻辑。
Swift 并不能直接调用 C++ 的方法,所以,我们需要使用 Object-C 进行封装:
1
2
3
4
5
6
7
8
9
10
11
12
13#import "MemoryPressureWrapper.h" #import "MemoryPressure.hpp" @implementation MemoryPressureWrapper + (size_t)thresholdForMemoryKillOfActiveProcess { MemoryPressure cpp; return cpp.thresholdForMemoryKillOfActiveProcess(1); } + (size_t)thresholdForMemoryKillOfInactiveProcess { MemoryPressure cpp; return cpp.thresholdForMemoryKillOfInactiveProcess(1); } @end
另外:如果 Object-C 调用 C++ 代码时,要将创建的
.m
文件后缀改成.mm
,告诉 XCode 编译该文件时要用到 C++ 代码。
直接引用 WebKit 的基础模块
在 WebKit 中,内存相关的方法在 WTF
和 bmalloc
模块中。我们可以下载下来源码,然后创建一个 APP 来引用。步骤如下:
- 下载 WebKit 源码,找到
Source/WTF
和Source/bmalloc
模块,和Tools/ccache
文件。 - 新建一个 WorkSpace:WKWebViewMemory,再新建、添加一个 iOS Project:WKWebViewMemoryApp。并将步骤 1 中的
Source/WTF
和Source/bmalloc
模块添加到 WorkSpace 中, - 在 WKWebViewMemoryApp 的 TARGETS 的 Build Settings 中,找到
Header Search Paths
,添加$(BUILT_PRODUCTS_DIR)/usr/local/include
、$(DSTROOT)/usr/local/include
和$(inherited)
。 - 因为
WTF
中的计算方法为 private 的,为了能在 app 中进行访问,需要修改为 public。
最终,就可以通过下面的方式进行获取了:
1
2
3
4
5
6
7
8
9
10
11
12#import "WTFWrapper.h" #import <wtf/MemoryPressureHandler.h> // 这个文件必须名称为 .mm 类型,否自会编译错误 @implementation WTFWrapper + (size_t)thresholdForMemoryKillOfActiveProcess { return WTF::thresholdForMemoryKillOfActiveProcess(1); } + (size_t)thresholdForMemoryKillOfInactiveProcess { return WTF::thresholdForMemoryKillOfInactiveProcess(1); } @end
运行结果
在 iPhoneXS 上运行的结果为:
1
2
3
4
5
6jetsamLimit 的值为:840MB 内存大小(memorySize)为:3778MB ramSize为:896MB 激活状态下(ActiveProcess)的最大可用内存为:8G 非激活状态下(InactiveProcess)的最大可用内存为:806M
奇怪的结果:在最大内存为 3778MB(不到 4G)的手机上,最大可用内存居然为 8G。
为什么?难道计算错了?难道内存没有限制?
我们来重新看一下获取最大可用内存的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13std::optional<size_t> MemoryPressureHandler::thresholdForMemoryKill() { if (m_configuration.killThresholdFraction) return m_configuration.baseThreshold * (*m_configuration.killThresholdFraction); switch (m_processState) { case WebsamProcessState::Inactive: return thresholdForMemoryKillOfInactiveProcess(m_pageCount); case WebsamProcessState::Active: return thresholdForMemoryKillOfActiveProcess(m_pageCount); } return std::nullopt; }
如果配置当中设置了 killThresholdFraction,则会通过 m_configuration.baseThreshold * (*m_configuration.killThresholdFraction);
进行计算。
我怀疑是抽离出来的方法,没有设置 killThresholdFraction,而在 iOS 系统中,在初始化 WKWebView 时,会设置此值,来返回一个合理的数值。
那在 iOS 系统中,thresholdForMemoryKill() 究竟会返回多少呢?可能只能通过 WKWebView 的源码进行获取了。
如果想获取最终可以运行的项目,可以查看 GitHub 上的 WKWebViewMemory。
参考
- iOS开发 - 在 Swift 中去调用 C/C++ 代码
最后
以上就是健康指甲油最近收集整理的关于WKWebView 线程终止的原因——之 OOM 的数值抽离 WebKit 的计算方法直接引用 WebKit 的基础模块运行结果参考的全部内容,更多相关WKWebView内容请搜索靠谱客的其他文章。
发表评论 取消回复