性能是客户端永恒的主题,咱们但愿用户在使用APP时,可以得到极致的性能体验,关注内存占用也正是由于这一点。git
(舒适提示:本片内容来源自WWDC21:Detect and diagnose memory issues)github
想了解更多WWDC2021内容的小伙伴,能够阅读我如下文章,欢迎多多交流和指正swift
一文带你读完WWDC21核心(新)技术点xcode
APP终极性能生存指南markdown
Faster application activationsapp
减小APP的内存占用,能够提升APP在后台存活的机会,从而让APP更快的被激活。ide
设备的内存空间是有限的,监控APP的内存使用状况,可以防止系统为了回收内存而主动终止APP。工具
这样即便APP在后台运行,也可以保存当前用户的使用状态,从而避免再次从内存加载致使的耗时。oop
Responsive experiencepost
更好的响应速度
策略性的将APP的内容加载到内存里,可以避免在用户使用APP时,因系统回收内存增长的等待耗时。
Complex features
让用户体验更丰富的功能
像加载视频、展现动画,这些复杂的功能每每须要占用更多的内存。所以有策略的使用内存,能够避免在用户使用APP的复杂功能时,被系统因内存占用问题而终止。
Wider device compatibility
更好的设备兼容性
让内存空间不太充足的老设备也能更好的使用APP的功能
Clean Memory
Dirty Memory
Clean Memory被分配,并写入内容后成为”脏内存“,脏内存包括:
Compressed Memory
压缩内存特指脏页(Dirty Pages)中暂未被访问的部分(Unaccessed pages),会在访问后解压,成为脏内存。
(下文会详细介绍脏页(Dirty Pages)的概念)
注:iOS中没有内存交换(Memory Swap)的概念,你可能在使用Instrument工具时,见到过Swapped
字段,实际上所指的是Compressed Memory
内存占用 = Dirty Memory + Compressed Memory
XCTest
经过单元测试和U测试中的XCTest来检测开发环境的性能指标
XCTest能够测量如下性能指标:
经过XCTest检测内存使用状况
func testSaveMeal() {
let app = XCUIApplication()
let options = XCTMeasuireOptions()
options.invocationOptions = [.manullyStart]
measure(metrics: [XCTMemoryMetric(application: app)],
options:options) {
app.launch()
startMeasureing()
app.cells.firstMatch.buttons["Save meal"].firstMatch.tap()
let savedButton = app.cells.firstMatch.buttons["Saved"].firstMatch
XCTAssertTure(savedButton.waitForExistence(timeout: 30))
}
}
复制代码
XCTest在Xcode 13中新增的两项收集性能状况的诊断产物
开启:enablePerformanceTestsDiagnostics YES
xcodebuild test
-project MealPannerApp.xcodeproj
-scheme PerformanceTests
-destination platform=iOS,name="iPhone"
-enablePerformanceTestsDiagnostics YES
复制代码
Ktrace files
Ktrace file可以打开,并展现如下分析报告:
Memory graphs
Memory graph展现APP进程的地址空间的快照
咱们能够生成任务开始(pre_XXX.memgraph)和结束(post_XXX.memgraph)两个时机的内存快照,从而查看这一阶段内存占用的变化。
MetricKit & Xcode Organizer
检测线上环境的内存指标
内存泄漏(Leaks)
最多见内存泄漏的缘由的是循环引用
经过命令行查看memgraph文件
堆大小问题(Heap size issues)
堆是进程地址空间中储存动态分配的对象的段(section),有策略性的加载和释放内存,能够避免内存峰值太高引起OOM。
vmmap -summary
对memgraph文件进行分析咱们主要关心Dirty Size
和Swapped Size
两列数据
对任务开始(pre_XXX.memgraph)和结束(post_XXX.memgraph)两个时机的内存快照进行对比
能够在下方看到按类聚合的,各种对象占用内存的大小
从图中能够看到,non-object
类型的数据,占用内存约13M。在Swift中,non-object
一般表明原始分配字节(raw malloced bytes)
打印memgraph文件中,类型为non-object
且占用内存超过500k的对象
打印对象的引用树
能够经过malloc_history -fullStacks
打印对象的分配调用栈
当不肯定是哪一个对象有问题时,能够经过leaks --referenceTree
,自顶向下打印进程的全部内存中最可疑的对象
--groupByType
参数,按type聚合简化打印结果。先介绍两个概念:页和”脏页“
- 页(Pages):是最小的独立的内存单元
- 一旦页中写入任何数据,都会使整页变成”脏页“
当内存准备写入新的数据时,系统会优先尝试使用脏页中的空闲内存,而立即将分配的内存过大时,即便空闲内存的总大小足够,但其并非一段连续的内存空间,仍会开辟一个新的脏页去写入这些数据。
这些没法被使用的空闲内存就是”碎片化内存“
再例如如下这种状况:咱们分配的对象总共使用了4个页,但每一个页的占用空间只占50%。
当咱们释放这些对象后,这些4个脏页的内存空间仍有50%的碎片化内存。
最理想化的状态应该将全部的分配的对象放在两页,以下图:
这样一旦释放这些对象时,仅会留有两个脏页,而且不存在碎片化内存。
咱们的目标:
vmmap -summary xx.memgraph
查看碎片化内存状况
DefaultMallocZone
平时开发者只须要关注这部分的内存空间,由于这是咱们进行堆分配默认结束的地方
MallocStackLoggingLiteZone
这个空间是全部堆分配结束的地方
使用Instruments工具中的Allocations track查看内存状况
其中的Destroyed和Persisted分别对应上面所描述的内存释放后的空闲内存(free memory)和仍未释放的内存(remaining objects)
回顾下咱们的整体排查流程: