东莞铭匠品质门窗 發表於 2021-1-6 20:40:00

分析字节跳动解决OOM的在线Memory Graph技术实现

<blockquote>
<p>之前看到字节团队分享的 “iOS性能优化实践:头条抖音如何实现OOM崩溃率下降50%+”这篇文章,对其实现比较感兴趣,但是没有开源,所以觉得自己花时间探索一下具体实现。</p>
</blockquote>
<p>什么是OOM,为什么会发生OOM以及其原因分析,大家去看原文就好了,本文主要分析APP内存使用到达阈值后,如何采集Memory Graph信息并分析。</p>
<h2 id="什么是memory-graph">什么是Memory Graph?</h2>
<p><img src="https://upload-images.jianshu.io/upload_images/12311242-4d899d303cd9857f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<p>这个时候就进入了断点模式,可以查看issue面板,注意选择右边Runtime:</p>
<p><img src="https://upload-images.jianshu.io/upload_images/12311242-76154a130c5e7d69.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<p>有很多叹号说明就有问题了。看内存中object的名字,有一条是Closure captures leaked。展开后点击就可以看到这个issue对应的内存图形展示在中间的面板中。</p>
<blockquote>
<p><strong>作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:413038000,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!</strong></p>
</blockquote>
<p><img src="https://upload-images.jianshu.io/upload_images/12311242-7d19f9f2f012e20d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<p>点击紫色的叹号会出现Xcode分析出来的内存引用图形:</p>
<p><img src="https://upload-images.jianshu.io/upload_images/12311242-792e0017dd09ca47.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<p>如果我们在线上App中也能采集到Memory Graph的相关信息,那么就可以在监控平台还原用户当时内存信息,帮助开发人员分析和解决问题。</p>
<h2 id="如何内存快照采集">如何内存快照采集</h2>
<ul>
<li>虚拟内存节点信息采集</li>
<li><code>OC/Swift/C++</code>&nbsp;所有实例及其类名</li>
<li>内存节点之间的引用关系,实例中成员变量类型和它引用其他内存的关系</li>
</ul>
<h3 id="内存节点的获取">内存节点的获取</h3>
<p>通过 mach 内核的<code>vm_region_recurse/vm_region_recurse64</code>函数我们可以遍历进程内所有<code>VM Region</code>,并通过<code>vm_region_submap_info_64</code>结构体获取以下信息:</p>
<ul>
<li>
<p>虚拟地址空间中的地址和大小。</p>
</li>
<li>
<p>Dirty 和 Swapped 内存页数,表示该<code>VM Region</code>的真实物理内存使用。</p>
</li>
<li>
<p>是否可交换,Text 段、共享 mmap 等只读或随时可以被交换出去的内存,无需关注。</p>
</li>
<li>
<p>user_tag,用户标签,用于提供该<code>VM Region</code>的用途的更准确信息。</p>
</li>
</ul>
<p><strong>获取所有内存节点信息</strong></p>
<pre><code>kern_return_t krc = KERN_SUCCESS;
vm_address_t address = 0;
vm_size_t size = 0;
uint32_t depth = 1;
pid_t pid = getpid();
char buf;
while (1) {
    struct vm_region_submap_info_64 info;
    mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
    krc = vm_region_recurse_64(mach_task_self(), &amp;address, &amp;size, &amp;depth, (vm_region_info_64_t)&amp;info, &amp;count);
    if (krc == KERN_INVALID_ADDRESS){
      break;
    }
    if (info.is_submap){
      depth++;
    } else {
      //do stuff
      proc_regionfilename(pid, address, buf, sizeof(buf));
      printf("Found VM Region: %08x to %08x (depth=%d) user_tag:%s name:%s\n", (uint32_t)address, (uint32_t)(address+size), depth, , buf);
      address += size;
    }
}
复制代码
</code></pre>
<p>输出如下:</p>
<pre><code>Found VM Region: 04c68000 to 04c80000 (depth=0) user_tag:(null) name:/private/var/containers/Bundle/Application/70EFA90E-D8EE-4FE9-9D5E-C708F6DB2FE6/OnlineMemoryGraphDemo.app/OnlineMemoryGraphDemo
Found VM Region: 04c80000 to 04c88000 (depth=0) user_tag:(null) name:/private/var/containers/Bundle/Application/70EFA90E-D8EE-4FE9-9D5E-C708F6DB2FE6/OnlineMemoryGraphDemo.app/OnlineMemoryGraphDemo
Found VM Region: 04c88000 to 04ca8000 (depth=0) user_tag:(null) name:/private/var/containers/Bundle/Application/70EFA90E-D8EE-4FE9-9D5E-C708F6DB2FE6/OnlineMemoryGraphDemo.app/OnlineMemoryGraphDemo
Found VM Region: 04ca8000 to 04cac000 (depth=0) user_tag:(null) name:/usr/lib/dyld
Found VM Region: 04cac000 to 04cb8000 (depth=0) user_tag:(null) name:/usr/lib/dyld
Found VM Region: 04cbc000 to 04d18000 (depth=0) user_tag:(null) name:/usr/lib/dyld
Found VM Region: 04d18000 to 04d20000 (depth=0) user_tag:(null) name:/usr/lib/dyld
Found VM Region: 04d24000 to 04d58000 (depth=0) user_tag:(null) name:/usr/lib/dyld
Found VM Region: 04d58000 to 04d94000 (depth=0) user_tag:(null) name:/usr/lib/dyld
Found VM Region: 04d98000 to 04da4000 (depth=0) user_tag:(null) name:/Developer/usr/lib/libBacktraceRecording.dylib
Found VM Region: 04da4000 to 04da8000 (depth=0) user_tag:(null) name:/Developer/usr/lib/libBacktraceRecording.dylib
Found VM Region: 04da8000 to 04dac000 (depth=0) user_tag:(null) name:/Developer/usr/lib/libBacktraceRecording.dylib
Found VM Region: 04dac000 to 04db0000 (depth=0) user_tag:(null) name:/Developer/usr/lib/libBacktraceRecording.dylib
Found VM Region: 04db0000 to 04db4000 (depth=0) user_tag:DYLIB name:/Developer/usr/lib/libMainThreadChecker.dylib
Found VM Region: 04db4000 to 04df8000 (depth=0) user_tag:(null) name:/Developer/usr/lib/libMainThreadChecker.dylib
Found VM Region: 04df8000 to 04dfc000 (depth=0) user_tag:(null) name:/Developer/usr/lib/libMainThreadChecker.dylib
Found VM Region: 04dfc000 to 04e00000 (depth=0) user_tag:(null) name:/Developer/usr/lib/libMainThreadChecker.dylib
Found VM Region: 04e00000 to 04f00000 (depth=0) user_tag:DYLIB name:/Developer/usr/lib/libMainThreadChecker.dylib
Found VM Region: 04f00000 to 04f04000 (depth=0) user_tag:(null) name:/Developer/usr/lib/libMainThreadChecker.dylib
Found VM Region: 04f04000 to 04f08000 (depth=0) user_tag:DYLIB name:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
Found VM Region: 04f08000 to 04f40000 (depth=0) user_tag:(null) name:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
Found VM Region: 04f40000 to 04f48000 (depth=0) user_tag:(null) name:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
Found VM Region: 04f48000 to 04f5c000 (depth=0) user_tag:(null) name:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
Found VM Region: 04f5c000 to 04f70000 (depth=0) user_tag:(null) name:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
Found VM Region: 04f70000 to 04f74000 (depth=0) user_tag:DYLIB name:/usr/lib/system/introspection/libdispatch.dylib
Found VM Region: 04f74000 to 04fbc000 (depth=0) user_tag:(null) name:/usr/lib/system/introspection/libdispatch.dylib
Found VM Region: 04fd4000 to 04fd8000 (depth=0) user_tag:(null) name:/usr/lib/system/introspection/libdispatch.dylib
Found VM Region: 04fd8000 to 04fdc000 (depth=0) user_tag:(null) name:/usr/lib/system/introspection/libdispatch.dylib
Found VM Region: 04fdc000 to 05008000 (depth=0) user_tag:(null) name:/usr/lib/system/introspection/libdispatch.dylib
Found VM Region: 05008000 to 0500c000 (depth=0) user_tag:DYLIB name:/Developer/Library/PrivateFrameworks/DebugHierarchyFoundation.framework/DebugHierarchyFoundation
Found VM Region: 0500c000 to 05030000 (depth=0) user_tag:(null) name:/Developer/Library/PrivateFrameworks/DebugHierarchyFoundation.framework/DebugHierarchyFoundation
Found VM Region: 05030000 to 05034000 (depth=0) user_tag:(null) name:/Developer/Library/PrivateFrameworks/DebugHierarchyFoundation.framework/DebugHierarchyFoundation
Found VM Region: 05034000 to 0503c000 (depth=0) user_tag:(null) name:/Developer/Library/PrivateFrameworks/DebugHierarchyFoundation.framework/DebugHierarchyFoundation
Found VM Region: 0503c000 to 05050000 (depth=0) user_tag:(null) name:/Developer/Library/PrivateFrameworks/DebugHierarchyFoundation.framework/DebugHierarchyFoundation
Found VM Region: 05050000 to 05054000 (depth=0) user_tag:DYLIB name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05054000 to 0505c000 (depth=0) user_tag:OS_ALLOC_ONCE name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 0505c000 to 05060000 (depth=0) user_tag:MALLOC name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05060000 to 05064000 (depth=0) user_tag:MALLOC name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05064000 to 05068000 (depth=0) user_tag:MALLOC name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05068000 to 05070000 (depth=0) user_tag:MALLOC name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05070000 to 05074000 (depth=0) user_tag:MALLOC name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05074000 to 05078000 (depth=0) user_tag:MALLOC name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05080000 to 05084000 (depth=0) user_tag:MALLOC name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05088000 to 0508c000 (depth=0) user_tag:MALLOC name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05090000 to 050d0000 (depth=0) user_tag:ANALYSIS_TOOL name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 050d8000 to 050dc000 (depth=0) user_tag:MALLOC_LARGE name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 050e0000 to 050e8000 (depth=0) user_tag:MALLOC_LARGE name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 050f4000 to 050f8000 (depth=0) user_tag:ANALYSIS_TOOL name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 050f8000 to 05100000 (depth=0) user_tag:ANALYSIS_TOOL name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05100000 to 05200000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05200000 to 05300000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05300000 to 05340000 (depth=0) user_tag:GENEALOGY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05340000 to 05348000 (depth=0) user_tag:ANALYSIS_TOOL name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05348000 to 0534c000 (depth=0) user_tag:MALLOC name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 0534c000 to 05350000 (depth=0) user_tag:MALLOC name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05350000 to 05354000 (depth=0) user_tag:MALLOC name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05354000 to 0535c000 (depth=0) user_tag:MALLOC name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 0535c000 to 05360000 (depth=0) user_tag:MALLOC name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05360000 to 053e0000 (depth=0) user_tag:MALLOC_LARGE name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 053e0000 to 053e4000 (depth=0) user_tag:MALLOC name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 053ec000 to 053f0000 (depth=0) user_tag:MALLOC name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 053f0000 to 053f4000 (depth=0) user_tag:LAYERKIT name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 053f4000 to 053f8000 (depth=0) user_tag:(null) name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 053f8000 to 05400000 (depth=0) user_tag:MALLOC_LARGE name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05400000 to 05500000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05500000 to 05600000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05600000 to 05700000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05700000 to 05800000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05800000 to 06000000 (depth=0) user_tag:MALLOC_SMALL name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 06000000 to 06800000 (depth=0) user_tag:MALLOC_SMALL name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 06800000 to 07000000 (depth=0) user_tag:MALLOC_SMALL name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 07000000 to 07800000 (depth=0) user_tag:MALLOC_SMALL name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 07800000 to 08000000 (depth=0) user_tag:MALLOC_SMALL name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 08000000 to 08100000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 08100000 to 08200000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 08200000 to 08300000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 08300000 to 0830c000 (depth=0) user_tag:(null) name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 0830c000 to 08310000 (depth=0) user_tag:(null) name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 08310000 to 08314000 (depth=0) user_tag:DYLIB name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 08314000 to 0831c000 (depth=0) user_tag:MALLOC_LARGE name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 0831c000 to 08320000 (depth=0) user_tag:FOUNDATION name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 08320000 to 0832c000 (depth=0) user_tag:(null) name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 0832c000 to 08334000 (depth=0) user_tag:(null) name:/usr/share/icu/icudt66l.dat
Found VM Region: 08334000 to 08340000 (depth=0) user_tag:MALLOC_LARGE name:/usr/share/icu/icudt66l.dat
Found VM Region: 08340000 to 08344000 (depth=0) user_tag:(null) name:/usr/share/icu/icudt66l.dat
Found VM Region: 08344000 to 0834c000 (depth=0) user_tag:MALLOC_LARGE name:/usr/share/icu/icudt66l.dat
Found VM Region: 0834c000 to 08350000 (depth=0) user_tag:LAYERKIT name:/usr/share/icu/icudt66l.dat
Found VM Region: 08350000 to 08360000 (depth=0) user_tag:MALLOC_LARGE name:/usr/share/icu/icudt66l.dat
Found VM Region: 08400000 to 08500000 (depth=0) user_tag:MALLOC_TINY name:/usr/share/icu/icudt66l.dat
Found VM Region: 08800000 to 09000000 (depth=0) user_tag:MALLOC_SMALL name:/usr/share/icu/icudt66l.dat
Found VM Region: 09000000 to 09800000 (depth=0) user_tag:MALLOC_SMALL name:/usr/share/icu/icudt66l.dat
Found VM Region: 09800000 to 0b750000 (depth=0) user_tag:(null) name:/usr/share/icu/icudt66l.dat
Found VM Region: 0b750000 to 0bf38000 (depth=0) user_tag:(null) name:/System/Library/Fonts/CoreUI/SFUI.ttf
Found VM Region: 0bf38000 to 108cc000 (depth=0) user_tag:(null) name:/System/Library/Fonts/LanguageSupport/PingFang.ttc
Found VM Region: 6b098000 to 6b09c000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b09c000 to 6b198000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b198000 to 6b19c000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b19c000 to 6b224000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b3c8000 to 6b3cc000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b3cc000 to 6b454000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b454000 to 6b458000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b458000 to 6b4e0000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b56c000 to 6b570000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b570000 to 6b5f8000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 8f018000 to 8f388000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 8f388000 to 8f38c000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 8f38c000 to 90000000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 90000000 to 911bc000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 911bc000 to 911c0000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 911c0000 to 92000000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 92000000 to 93130000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 93130000 to 93134000 (depth=0) user_tag:SHARED_PMAP name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 93134000 to 94000000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 94000000 to a0000000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a0000000 to a2ca4000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a2ca4000 to a2ca8000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a2ca8000 to a2cac000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a2cac000 to a2cc0000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a2cc0000 to a2cc4000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a2cc4000 to a2cc8000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a2ccc000 to a2da8000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a2da8000 to a2dac000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a2dac000 to b0000000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: b0000000 to c0000000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: c0000000 to d0000000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: d0000000 to db010000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: dd010000 to de000000 (depth=0) user_tag:UNSHARED_PMAP name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: de000000 to e0000000 (depth=0) user_tag:UNSHARED_PMAP name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: e0000000 to e2000000 (depth=0) user_tag:UNSHARED_PMAP name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: e2000000 to e4000000 (depth=0) user_tag:UNSHARED_PMAP name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: e4000000 to e6000000 (depth=0) user_tag:UNSHARED_PMAP name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: e6000000 to e8000000 (depth=0) user_tag:UNSHARED_PMAP name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: e8000000 to e9da8000 (depth=0) user_tag:UNSHARED_PMAP name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: ebda8000 to f0000000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: f0000000 to f7150000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 80000000 to a0000000 (depth=0) user_tag:MALLOC_NANO name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: c0000000 to 00000000 (depth=0) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 00000000 to 00000000 (depth=0) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
复制代码
</code></pre>
<p><strong>内存节点大致分为这几类:</strong></p>
<ul>
<li>
<p>App的二进制文件在内存的映射(如OnlineMemoryGraphDemo)</p>
</li>
<li>
<p>动态库在内存中的映射(如libBacktraceRecording.dylib,libdispatch.dylib等)</p>
</li>
<li>
<p>系统或自定义字体等资源(SFUI.ttf, PingFang.ttc)</p>
</li>
<li>
<p>栈区(STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64)</p>
</li>
<li>
<p>Malloc Zone,Malloc Zone分为Nano和Scalable,Nano分配16B~256B(16B的整数倍)的内存,Scalable分配256B以上的大内存</p>
</li>
</ul>
<h3 id="malloc内存信息的获取">Malloc内存信息的获取</h3>
<p>Malloc内就是我们平时开发接触最多的了,我们iOS开发中语言,C/C++, OC, Swift等创建的内存和对象都是在这里分配。</p>
<p>我们可以通过<code>malloc_get_all_zones</code>获取<code>libmalloc</code>内部所有的<code>zone</code>,并遍历每个<code>zone</code>中管理的内存节点,获取 libmalloc 管理的存活的所有内存节点的指针和大小。</p>
<p><strong>获取所有Malloc Zone</strong></p>
<pre><code>vm_address_t *zones = NULL;
unsigned int zoneCount = 0;
kern_return_t result = malloc_get_all_zones(TASK_NULL, memory_reader_callback, &amp;zones, &amp;zoneCount);
if (result == KERN_SUCCESS) {   
    for (unsigned int i = 0; i &lt; zoneCount; i++) {      
      malloc_zone_t *zone = (malloc_zone_t *)zones;               
      printf("Found zone name:%s\n", zone-&gt;zone_name);
    }
}
复制代码
</code></pre>
<blockquote>
<p>输出如下:</p>
</blockquote>
<pre><code>Found zone name:DefaultMallocZone   // Nano Zone
Found zone name:MallocHelperZone.   // Scalable Zone
Found zone name:QuartzCore
复制代码
</code></pre>
<p><strong>获取Zone内所有分配的节点</strong></p>
<pre><code>malloc_introspection_t *introspection = zone-&gt;introspect;

if (!introspection) {
    continue;
}

void (*lock_zone)(malloc_zone_t *zone)   = introspection-&gt;force_lock;
void (*unlock_zone)(malloc_zone_t *zone) = introspection-&gt;force_unlock;

// Callback has to unlock the zone so we freely allocate memory inside the given block
malloc_object_enumeration_block_t callback = ^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
    unlock_zone(zone);
    block(object, actualClass);
    lock_zone(zone);
};

BOOL lockZoneValid = mallocPointerIsReadable((void *)lock_zone);
BOOL unlockZoneValid = mallocPointerIsReadable((void *)unlock_zone);

// There is little documentation on when and why
// any of these function pointers might be NULL
// or garbage, so we resort to checking for NULL
// and whether the pointer is readable
if (introspection-&gt;enumerator &amp;&amp; lockZoneValid &amp;&amp; unlockZoneValid) {
    lock_zone(zone);
    introspection-&gt;enumerator(TASK_NULL, (void *)&amp;callback, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, memory_reader_callback, &amp;vm_range_recorder_callback);
    unlock_zone(zone);
}
复制代码
</code></pre>
<h3 id="判断内存地址中是什么">判断内存地址中是什么</h3>
<blockquote>
<p>这里遇到一个问题,遍历Malloc Zone分配的内存节点,只会给一个地址和大小,怎么判断这是一个OC/C++/Swift对象还是只是一个Malloc出来的Buffer呢?</p>
</blockquote>
<ul>
<li>判断是OC/Swift对象</li>
<li>判断是C++对象</li>
<li>一块普通Buffer</li>
</ul>
<p><strong>如何判断是不是一个OC/Swift对象</strong></p>
<p><code>Swift</code>在内存布局上兼容了<code>Objective-C</code>,也有<code>isa</code>指针,<code>objc</code>相关方法可以作用于两种语言的对象上。只要保证 isa 指针合法,对象实例大小满足条件即可认为正确。</p>
<p><strong>获取所有OC/SwiftClass类型</strong></p>
<pre><code>CFMutableSetRef registeredClasses;

unsigned int updateRegisteredClasses() {
    if (!registeredClasses) {
      registeredClasses = CFSetCreateMutable(NULL, 0, NULL);
    } else {
      CFSetRemoveAllValues(registeredClasses);
    }
    unsigned int count = 0;
    Class *classes = objc_copyClassList(&amp;count);
    for (unsigned int i = 0; i &lt; count; i++) {
      CFSetAddValue(registeredClasses, (__bridge const void *)(classes));
    }
    free(classes);
    return count;
}
复制代码
</code></pre>
<p><strong>判断isa是否合法</strong></p>
<pre><code>typedef struct {
    Class isa;
} malloc_maybe_object_t;

void vm_range_recorder_callback(task_t task, void *context, unsigned type, vm_range_t *ranges, unsigned rangeCount) {
    if (!context) {
      return;
    }

    for (unsigned int i = 0; i &lt; rangeCount; i++) {
      vm_range_t range = ranges;
      malloc_maybe_object_t *tryObject = (malloc_maybe_object_t *)range.address;
      Class tryClass = NULL;
#ifdef __arm64__
      // See http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html
      extern uint64_t objc_debug_isa_class_mask WEAK_IMPORT_ATTRIBUTE;
      tryClass = (__bridge Class)((void *)((uint64_t)tryObject-&gt;isa &amp; objc_debug_isa_class_mask));
#else
      tryClass = tryObject-&gt;isa;
#endif
      // 1\. 判断是否为OC/SwiftObject
      if (CFSetContainsValue(registeredClasses, (__bridge const void *)(tryClass))) {
            (*(malloc_object_enumeration_block_t __unsafe_unretained *)context)((__bridge id)tryObject, tryClass);
      }
      // 2\. 判断是否是一个保护type_info的C++对象
      else if ( != NULL) {
            NSLog(@"Find a Cpp Object:%s!", );
      }
    }
}
复制代码
</code></pre>
<p><strong>如何判断是不是一个C++对象</strong></p>
<p>C++对象根据是否包含虚表可以分成两类。对于不包含虚表的对象,因为缺乏运行时数据,无法进行处理。对于对于包含虚表的对象,在调研 mach-o 和 C++的 ABI 文档后,可以通过 std::type_info 和以下几个 section 的信息获取对应的类型信息。</p>
<p>[图片上传中...(image-1c32a1-1607149856673-1)]</p>
<p>C++实例以及 vtable,type_info的引用关系示意图</p>
<blockquote>
<p>分析MachO文件,我们发现在Dynamic Loader Info段中,发现了C++Type_Info信息</p>
</blockquote>
<p>[图片上传中...(image-c2b44f-1607149856672-0)]</p>
<blockquote>
<p>获取App二进制加载到内存中起始地址,_dyld_register_func_for_add_image方法当App的二进制或者动态库等MachO格式的文件映射到内存后,启动App时的回调,我们可以通过这个拿到App执行二进制的起始地址,从而拿到段中的C++类型信息。</p>
</blockquote>
<p><strong>获取App二进制起始地址</strong></p>
<pre><code>/*
* The following functions allow you to install callbacks which will be called   
* by dyld whenever an image is loaded or unloaded.During a call to _dyld_register_func_for_add_image()
* the callback func is called for every existing image.Later, it is called as each new image
* is loaded and bound (but initializers not yet run).The callback registered with
* _dyld_register_func_for_remove_image() is called after any terminators in an image are run
* and before the image is un-memory-mapped.
*/
extern void _dyld_register_func_for_add_image(void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide))    __OSX_AVAILABLE_STARTING(__MAC_10_1, __IPHONE_2_0);
复制代码
</code></pre>
<p><strong>获取所有C++type_info</strong></p>
<pre><code>typedef std::vector&lt;struct segment_command_64 const *&gt;    Segment64Vector;
typedef std::set&lt;uint64_t *&gt;    CxxTypeInfoSet;

static Segment64Vector *segments_64 = NULL;
static CxxTypeInfoSet *cxxTypeInfoSet = NULL;

// 记录Data Segment中__const段的有效最大最小地址,合法的C++ type_info地址不会超出这里
uint64_t dataConstMinAddress = NULL;
uint64_t dataConstMaxAddress = NULL;

static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide)
{
    // 这里只分析App的二进制
    if (mhp-&gt;filetype != MH_EXECUTE) {
      return;
    }

    segments_64 = new Segment64Vector();
    cxxTypeInfoSet = new CxxTypeInfoSet();
    size_t header_size = sizeof(struct mach_header_64);
    uint64_t *load_comandPtr = (uint64_t *)((unsigned char *)mhp + header_size);
    uint64_t address = (uint64_t)((uint64_t *)mhp);
    uint32_t ptrSize = sizeof(uint64_t);
    for (int i=0; i&lt;mhp-&gt;ncmds; i++) {
      struct load_command *load_command = (struct load_command *)load_comandPtr;
      segments_64-&gt;push_back((struct segment_command_64 const *)load_command);
      NSString *cmdType = loadCommandMap[@(load_command-&gt;cmd)];
      NSLog(@"dyld_callback load_command cmd:%@", cmdType);
      // 分析 Data Segment中__const段,获取有效最大最小地址
      if (load_command-&gt;cmd == LC_SEGMENT_64) {
            struct segment_command_64 *segment_64 = (struct segment_command_64 *)load_command;
            if (strcmp(segment_64-&gt;segname, "__DATA") == 0) {
                const struct section_64 *sec = (struct section_64 *)(segment_64 + 1);
                for (int j=0; j&lt;segment_64-&gt;nsects; j++) {
                  if (strcmp(sec.sectname, "__const") == 0) {
                        dataConstMinAddress = (((uint64_t)(uint64_t *)mhp) + sec.offset);
                        dataConstMaxAddress = (((uint64_t)(uint64_t *)mhp) + sec.offset + sec.size);
                  }
                }
            }

      }
      // 分析动态链接段的信息,获取App内C++ type_info的地址
      else if (load_command-&gt;cmd == LC_DYLD_INFO ||
                   load_command-&gt;cmd == LC_DYLD_INFO_ONLY) {
            struct dyld_info_command *dyldCommand = (struct dyld_info_command *)load_command;
            uint8_t *bytePtr = (uint8_t *)((uint8_t *)mhp + dyldCommand-&gt;bind_off); // Dynamic Loader Info Bind部分的起始地址
            uint64_t dyldMaxAddress = (((uint64_t)(uint64_t *)mhp) + dyldCommand-&gt;bind_off + dyldCommand-&gt;bind_size);
            uint64_t doBindLocation = *((uint64_t *)bytePtr);

            int32_t libOrdinal = 0;
            uint32_t type = 0;
            int64_t addend = 0;
            NSString * symbolName = nil;
            uint32_t symbolFlags = 0;
            BOOL isDone = NO;
            while (((uint64_t)(uint64_t *)bytePtr) &lt; dyldMaxAddress) {
                uint8_t byte = read_int8(&amp;bytePtr);
                uint8_t opcode = byte &amp; BIND_OPCODE_MASK;
                uint8_t immediate = byte &amp; BIND_IMMEDIATE_MASK;
                NSLog(@"dyld_callback load_command opcode:%d, immediate:%d", opcode, immediate);
                switch (opcode)
                {
                  case BIND_OPCODE_DONE:
                        // The lazy bindings have one of these at the end of each bind.
                        isDone = YES;

                        doBindLocation = (*((uint64_t *)bytePtr) + 1);

                        break;

                  case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
                        libOrdinal = immediate;
                        break;

                  case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:

                        libOrdinal = (uint32_t)read_uleb128(&amp;bytePtr);

                        break;

                  case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
                  {
                        // Special means negative
                        if (immediate == 0)
                        {
                            libOrdinal = 0;
                        }
                        else
                        {
                            int8_t signExtended = immediate | BIND_OPCODE_MASK; // This sign extends the value

                            libOrdinal = signExtended;
                        }
                  } break;

                  case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
                        symbolFlags = immediate;
                        symbolName = read_string(&amp;bytePtr);
                        break;

                  case BIND_OPCODE_SET_TYPE_IMM:
                        type = immediate;
                        break;

                  case BIND_OPCODE_SET_ADDEND_SLEB:

                        addend = read_sleb128(&amp;bytePtr);
                        break;
                        //
                  case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
                  {
                        uint32_t segmentIndex = immediate;
                        uint64_t val = read_uleb128(&amp;bytePtr);

                        if (segmentIndex &lt; segments_64-&gt;size())
                        {
                            address += (*segments_64)-&gt;fileoff + val;
                        }
                  } break;

                  case BIND_OPCODE_ADD_ADDR_ULEB:
                  {
                        uint64_t val = read_uleb128(&amp;bytePtr);
                        address += val;
                  } break;

                  case BIND_OPCODE_DO_BIND:
                  {
                        // 获取C++ type_info地址
                        NSLog(@"dyld_callback Bind SymbolName:%@", symbolName);
                        if () {
                            std::type_info *type_info = (std::type_info *)address;
                            NSLog(@"std::type_info name:%s address:%p", type_info-&gt;name(), type_info);
                            cxxTypeInfoSet-&gt;insert((uint64_t *)address);
                        }
                        doBindLocation = *((uint64_t *)bytePtr);

                        address += ptrSize;
                  } break;

                  case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
                  {
                        uint64_t startNextBind = *((uint64_t *)bytePtr);

                        uint64_t val = read_uleb128(&amp;bytePtr);
                        doBindLocation = startNextBind;

                        address += ptrSize + val;
                  } break;

                  case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
                  {
                        uint32_t scale = immediate;
                        // 获取C++ type_info地址
                        if () {
                            std::type_info *type_info = (std::type_info *)address;
                            NSLog(@"std::type_info name:%s address:%p", type_info-&gt;name(), type_info);
                            cxxTypeInfoSet-&gt;insert((uint64_t *)address);
                        }

                        doBindLocation = *((uint64_t *)bytePtr);

                        address += ptrSize + scale * ptrSize;
                  } break;

                  case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
                  {
                        uint64_t startNextBind = *((uint64_t *)bytePtr);

                        uint64_t count = read_uleb128(&amp;bytePtr);

                        uint64_t skip = read_uleb128(&amp;bytePtr);

                        for (uint64_t index = 0; index &lt; count; index++)
                        {
                            doBindLocation = startNextBind;

                            address += ptrSize + skip;
                        }
                  } break;

                  default:
                        break;
                }
            }
      }
      load_comandPtr = (uint64_t *)((unsigned char *)load_comandPtr + load_command-&gt;cmdsize);
    }
}
复制代码
</code></pre>
<blockquote>
<p>输出如下</p>
</blockquote>
<pre><code>2020-11-16 11:03:32.879783+0800 OnlineMemoryGraphDemo std::type_info name:4Base address:0x104c803c0
2020-11-16 11:03:32.880196+0800 OnlineMemoryGraphDemo std::type_info name:5Human address:0x104c803d0
2020-11-16 11:03:32.880761+0800 OnlineMemoryGraphDemo std::type_info name:P4Male address:0x104c803f8
2020-11-16 11:03:32.881309+0800 OnlineMemoryGraphDemo std::type_info name:4Male address:0x104c803e0
复制代码
</code></pre>
<p><strong>实际App中关于C++类型的定义</strong></p>
<pre><code>class Human {
public:
    int age;
    int sex;

    void sayHello();
};

class Base
{
public:

    Base(int i) :baseI(i){};

    int getI(){ return baseI; }

    static void countI(){};

    virtual ~Base(){}

    virtual void basePrint(void){ printf("Base::print()");}

private:

    int baseI;

    static int baseS;
};

class Male : public Base {
public:
    Male(int i): Base(i){

    };
    void sayHello();
};
复制代码
</code></pre>
<p><strong>判断一个地址是否为一个C++Object(有type_info的)</strong></p>
<pre><code>typedef std::set&lt;uint64_t *&gt;    CxxTypeInfoSet;
static CxxTypeInfoSet *cxxTypeInfoSet = NULL;

+ (const char *) cppTypeInfoName:(void *) ptr {
    uint64_t *typeInfoPtr = (uint64_t*)(*((uint64_t *)ptr) - 8);
    uint64_t typeInfoAddress = (uint64_t)typeInfoPtr;
    if (typeInfoAddress &gt;= dataConstMinAddress &amp;&amp; typeInfoAddress &lt; dataConstMaxAddress) {
      uint64_t *typeInfo = (uint64_t *)(*typeInfoPtr);
      if (cxxTypeInfoSet-&gt;find(typeInfo) != cxxTypeInfoSet-&gt;end()) {
            const char *name = ((std::type_info *)typeInfo)-&gt;name();
            return name;
      }
    }
    return NULL;
}
复制代码
</code></pre>
<blockquote>
<p>输出如下</p>
</blockquote>
<pre><code>2020-11-16 11:03:39.581065+0800 OnlineMemoryGraphDemo Find a Cpp Object:4Male!
2020-11-16 11:03:39.581137+0800 OnlineMemoryGraphDemo Find a Cpp Object:4Base!
复制代码
</code></pre>
<h2 id="数据上报和后台分析">数据上报和后台分析</h2>
<p>这个大家就根据自己情况自己去实现吧。</p>
<blockquote>
<p><strong>作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:413038000,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!</strong></p>
</blockquote>
<p>作者:有点特色<br>
链接:https://juejin.cn/post/6895583288451465230<br>
来源:掘金</p><br><br>
来源:https://www.cnblogs.com/iOSer1122/p/14243344.html
頁: [1]
查看完整版本: 分析字节跳动解决OOM的在线Memory Graph技术实现