feat: refine

This commit is contained in:
LiuBinPeng
2022-05-30 15:02:31 +08:00
parent 538801e651
commit 6cd0cf5144
60 changed files with 135 additions and 1487 deletions

View File

@@ -10,7 +10,7 @@ NSTimer、CADisplayLink 的 基础 API `[NSTimer scheduledTimersWithTimeInterval
栈、堆、BSS、数据段、代码段
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/iOS-MemoryLayout.png)
![](./../assets/iOS-MemoryLayout.png)
stack又称作堆栈用来存储程序的局部变量但不包括static声明的变量static修饰的数据存放于数据段中。除此之外在函数被调用时栈用来传递参数和返回值。栈内存地址越来越少
@@ -147,7 +147,7 @@ Demo1
运行该代码会 Crash报错信息如下
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/TaggedPointerCrash.png)
![](./../assets/TaggedPointerCrash.png)
说明:一开始的报错信息只说坏内存访问,但是并没有显示具体的方法调用堆。想知道具体 Crash 原因还是需要看看堆栈比较方便。输入 bt 查看最后是由于 `objc_release` 方法造成 crash。
@@ -257,7 +257,7 @@ static inline bool _objc_isTaggedPointer(const void * _Nullable ptr)
tips某些对象虽然是 TaggedPointer 类型,但是打印 class 发现不是,猜测可能是系统用类簇隐藏了某些实现细节。比如下面
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/NSTaggedPointerOfNSNumber.png)
![](./../assets/NSTaggedPointerOfNSNumber.png)
针对 NSNumber 的 TaggedPoniter 的 case查看 class 打印出 `__NSCFNumber`。但根据源码和内存高地址位分析确实是 TaggedPoniter。
@@ -826,7 +826,7 @@ void sel_init(size_t selrefCount){
在 gone 处加断点,利用 runtime 查看类中的方法信息
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/cxx_destructDemo1.png)
![](./../assets/cxx_destructDemo1.png)
发现存在 `.cxx_destruct` 方法。
@@ -861,7 +861,7 @@ void sel_init(size_t selrefCount){
@end
```
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/cxx_destructdemo3.png)
![](./../assets/cxx_destructdemo3.png)
Tips@property 会自动生成成员变量,另外类后面加 `{}` 在内部也可以加成员变量,假如成员变量是对象类型,比如 NSString则叫实例变量。
@@ -875,7 +875,7 @@ Tips@property 会自动生成成员变量,另外类后面加 `{}` 在内部
在 gone 的地方加断点,输入 `watchpoint set variable p->_name`,则会将 `_name` 实例变量加入 watchpoint当变量被修改时会触发断点可以看出从某个值变为 0x0也就是 nil。此时边上调用堆栈显示在 `objc_storestrong` 方法中,被设置为 nil.
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/cxx_destructDemo2.png)
![](./../assets/cxx_destructDemo2.png)
### 深入 .cxx_destruct
@@ -1123,7 +1123,7 @@ class AutoreleasePoolPage {
- 每个 AutoreleasePoolPage 对象占用 4096 字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放 autorelease 对象的地址
- 所有的 AutoreleasePoolPage 对象通过**双向链表**的形式连接在一起。child 指向下一个对象parent 指向上一个对象
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/autoreleasepool.png)
![](./../assets/autoreleasepool.png)
```objectivec
id * begin() {
@@ -1181,7 +1181,7 @@ int main(int argc, const char * argv[]) {
main 方法内部3个 autoreleasepool 底层怎么样工作的?
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/AutoreleasePoolMoreItem.png)
![](./../assets/AutoreleasePoolMoreItem.png)
3个@auto releasepool 系统遇到第一个的时候底层就是初始化一个结构体 `__AtAutoreleasePool`,结构体构造方法内部调用 `AutoreleasePoolPage::push` 方法,系统给 AutoreleasePoolPage 真正保存 autorelease 对象的地方存储进一个 `POOL_BOUNDARY` 对象,然后储存 P1、P2 对象地址,遇到第二个则继续初始化结构体,调用 push 方法,存储一个` POOL_BOUNDARY` 对象,继续保存 P3遇到第三个则继续初始化结构体调用 push 方法,存储一个 `POOL_BOUNDARY` 对象,继续保存 P4。
@@ -1981,7 +1981,7 @@ iOS 在主线程的 Runloop 中注册了2个 Observer
结合 RunLoop 运行图
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/RunLoop-SourceCode.png)
![](./../assets/RunLoop-SourceCode.png)
- 01 通知 Observer 进入 Loop 会调用 `objc_autoreleasePoolPush`
@@ -2070,7 +2070,7 @@ NSHashMap、NSMapTable 都可以描述 key、value 的内存修饰。
这段代码运行会 crash信息如下
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/NSErrorZombieCrash.png)
![](./../assets/NSErrorZombieCrash.png)
原因是 NSError 构造方法内部会加 autorelease。源码如下
@@ -2133,8 +2133,28 @@ MRC 下的 `[(id)(object) autorelease]` 等价于 ARC 下的 `id __autoreleasing
我写了个僵尸对象检测工具,效果如下
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/ZombieSniffer.png)
![](./../assets/ZombieSniffer.png)
可以定位僵尸对象,并且打印出具体堆栈,并模拟系统行为调用 `abort` 。对监控原理和工具实现感兴趣的可以查看这里[带你打造一套 APM 监控系统-内存监控之野指针/内存泄漏监控](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.74.md#zombieSniffer)
Demo [👇这里](https://github.com/FantasticLBP/BlogDemos/tree/master/僵尸对象探针)
Demo [👇这里](https://github.com/FantasticLBP/BlogDemos/tree/master/僵尸对象探针)
### 内存是连续的吗?
应用启动后Mach-O 文件是分段载入内存的。我们使用的内存都是虚拟内存,通过内存映射表来做。
每个进程在创建加载时会被分配一个大小大概为12倍真实地内存的连续虚拟地址空间让当前软件认为自己拥有一块很大内存空间。实际上是把磁盘的一小部分作为假想内存来使用。
CPU 不直接和物理内存打交道,而是通过 MMUMemory Manage Unit内存管理单元MMU 是一种硬件电路,速度很快,主要工作是内存管理,地址转换是功能之一。
每个进程都会有自己的页表 `Page Table` 页表存储了进程中虚拟地址到物理地址的映射关系所以就相当于地图。MMU 收到 CPU 的虚拟地址之后就开始查询页表,确定是否存在映射以及读写权限是否正常。
iOS 程序在进行加载时,会根据一 page 大小16kb 将程序分割为多页启动时部分的页加载进真实内存部分页还在磁盘中中间的调度记录在一张内存映射表Page Table这个表用来调度磁盘和内存两者之间的数据交换。
如上图App 运行时执行某个任务时会先访问虚拟页表如果页表的标记为1则说明该页面数据已经存在于内存中可以直接访问。如果页表为0则说明数据未在物理内存中这时候系统会阻塞进程叫做缺页中断page fault进程会从用户态切换到内核态并将缺页中断交给内核的 page Fault Handler 处理。等将对应的 page 从磁盘加载到内存之后再进行访问,这个过程叫做 page in。
因为磁盘访问速度较慢,所以 page in 比较耗时,而且 iOS 不仅仅是将数据加载到内存中,还要对这页做 Code Sign 签名认证,所以 iOS 耗时更长
TipsCode Sign 加密哈希并不少针对于整个文件,而是针对于每一个 Page 的,保证了在 dyld 进行加载的时候,可以对每一个 page 进行独立验证。
等到程序运行时用到了才去内存中寻找虚拟地址对应的页帧,找不到才进行分配,这就是内存的惰性(延时)分配机制。