Unit 5

什么是垃圾回收机制

垃圾回收(Garbage Collection,简称GC)的理论主要基于一个事实:大部分对象的生命周期都很短。所以GC将内存中的对象主要分成两个区域:Young区和Old区。对象先在Young区被创建,然后如果经过一段时间还存活着,则被移动到Old区。由于这两个区里的对象特点不同,采用的内存回收算法也不同。

Young区的对象因为大部分生命周期都很短,每次回收之后只有少部分能够存活,所以采用的算法叫Copying算法,即直接将活着的对象复制到另一个地方。而Young区内部又分成了三块区域:Eden区、From区、To区。每次执行Copying算法时,就将存活的对象从Eden区和From区复制到To区,然后交换From区和To区的名字,即From区变成To区,To区变成From区。

因为Old区的对象都是存活下来的老司机了,所以如果用Copying算法的话,很可能90%的对象都得复制一遍,所以Old区的回收算法叫Mark-Sweep(标记-清除)算法。简单来说,就先把不用的对象标记(Mark)出来,然后回收(Sweep),活着的对象就原封不动地保留下来。因为大部分对象都活着,所以回收的对象并不多。但是这个算法会有一个问题:产生内存碎片(分配给对象的内存之间的内存空洞,也就是说下一个对象的内存地址起始点不是上一个对象内存地址的结束点),故它需要整理内存碎片的逻辑——Compact(紧凑,这个此用没有觉得熟悉,嗯!对,sizeClass)算法,粗略地讲就是将对象插入到这些空洞里,当然这里面还有很多细节,比如空洞的内存大小是否足够容纳将要插入的对象,这里就不一一展开了。

接下来的问题就是GC如何找到需要回收的垃圾对象了。为了避免ARC解决不了的问题——循环引用,GC引入了一个叫可达性的概念。[注解]

GC工作时,GC认为当前的一些对象是有效的,包括:全局变量、栈里面的变量等,然后GC从这些变量出发,去标记这些变量可达的其他变量。这个标记是一个递归过程,最后就像从树根的内存对象开始,把所有的枝叶都标成可达的了。那除了这些可达的变量,别的变量就都是需要被回收了。

而实际上AppleOS X 10.5时也是采用的GC,在10.7时就换成了ARCApple不能忍受的问题是:垃圾回收时,整个程序需要暂停——Stop the World,这便是Android手机有时会卡的原因。当所有对象都需要回收时(关闭应用,新开应用最容易造成卡顿!),那种体验可想而知。而后面的系统升级,GC的优化一直没有停歇过,所以现实情况并没有太糟。


注解:iOS开发中导致循环引用是因为两个对象之间相互强引用,在ARC的内存管理方式下两者均不能被释放,而GC不是这样的。在《Java Platform Performance: Strategies and Tactics》这本书的附录A中有一处说明:

“It’s important to note that not just any strong reference will hold an object in memory. These must be references that chain from a garbage collection root. GC roots are a special class of variable that includes

  • Temporary variables on the stack (of any thread)
  • Static variables (from any class)
  • Special references from JNI native code”。

意思是说:强引用本身并不一定会导致对象的内存不能被回收,当这些引用是由GC roots直接或间接指向的,对象才不会被销毁。GC roots是特殊的一类变量,包括:线程中的局部变量、任何类的静态变量(这就是为什么要少用静态变量的原因)、被JNI引用的变量。

下面这两段代码有什么问题?

//第一段
@property (copy) NSMutableArray<NSImage *> *photos;

- (void)replaceWithStockPhoto:(NSImage *)stockPhoto {
    self.photos = [NSMutableArray<NSImage *> new];

    [self.photos addObject:stockPhoto];
}

//第二段
 - (BOOL)validateDictionary:(NSDictionary *)dict usingChecker:(Checker *)checker error:(NSError **)error {
    __block BOOL isValid = YES;

    [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
        if ([checker checkObject:obj forKey:key]) {
            return;
        }

        *stop = YES; 
        isValid = NO;

        if(error) {
            *error = [NSError errorWithDomain:...];
        }
    }];

    return isValid;
}

解答一:

  1. 声明属性时没有使用nonatomic修饰,默认为atomic,降低访问效率;
  2. 可变数组不可变赋值的结果是不可变的,所以当向不可变的数组对象发送添加元素的消息运行时会崩溃:unrecognized selector send to instance XXX。

解答二:
在枚举遍历方法内部系统会自动添加一个@autoreleasepool,而NSError **,该参数会被编译器自动重写为NSError * __autoreleasing * 形式。所以这个error 对象在离开这个自动释放池时就被释放了,如果外部访问这个error 对象时,就会出现EXC_BAD_ACCESS (野指针)错误。如果想要在外部访问这个error 对象,可以先在遍历前创建一个临时的NSError 实例,然后在遍历结束后对传入的error 赋值。就像这样:

//..
__block BOOL isValid = YES;
__block NSError *result = nil;

[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
    if ([checker checkObject:obj forKey:key]) {
        return;
    }

    *stop = YES; 
    isValid = NO;

    if(error) {
        result = [NSError errorWithDomain:@"错误" code:0 userInfo:nil];
    }
}];

    *error = result;
//...

Objective-C对象内存结构中的isa指针有什么用?

在面向对象的世界里,一切都应该是对象。而Objective-C却没有完全做到这一点,像NSIntegerCGPoint这些都是基本的数值类型,并不具备对象的特征。所以Objective-C提供了将它们包装成类的NSNumberNSValue。在Objective-C中,我们新建的一个对象肯定是某个类的实例,并且类中定义的方法是其所有的对象共享的(节约内存)。为了在对象接收到消息时能够准确快速的响应,那么此时就需要建立对象与类的联系,Objective-C给出的解决方案是isa(按照苹果的命名,姑且将它读为艾萨,也就是is a kind of的意思)。其实打开运行时文件(shift + command + O,输入runtime.h),就可以看到类和对象的定义:

这里写图片描述

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

//在这里我们可以看到对象和类的本质是结构体

Class是这样定义的:

typedef struct objc_class *Class;

对象的艾萨自然而然就指向了它所属的类。那么现在类本身的艾萨又指向何处呢?根据前面所说的面向对象的设计原则,类本身也应当是对象,它的父类指针指向它的直接父类,它的艾萨指向的是一个被叫做元类(metaclass)的对象。根据这个方法,我们就可确定它的存在了:

OBJC_EXPORT Class objc_getMetaClass(const char *name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);

而这个叫做元类的家伙是用来保存类方法列表的(至于为什么不让类对象直接保存而新增这样子一个概念的原因不太明确,猜测是为了继承关系上的设计完整,还有就是为了实现一些黑魔法,如KVO)。当一个类对象收到消息时,元类后先查找本身是否有该类方法的实现,没有就沿着父类指针查找直到继承链的尽头。

既然元类也是对象,它也应当有父类指针和艾萨。它的父类指针指向的是它的类的父类的元类(淡定,后面会有图示),而它的艾萨就直接指向NSObject元类。NSObject的元类的艾萨指向它自己,形成一个闭环。它的父类指针就指向NSObject类本身。因为NSObject是根类(Swift中不是),所以它没有父类指针。图示如下:

这里写图片描述

总结之:艾萨指针可以帮助类对象找到消息的实现,并建立实例与类的联系(于2017-8-8补:对象的实例方法没有存储在对象的结构体中,对象将通过自己艾萨来查找所属类的实例方法列表,通过指向父类的指针super来查找父类方法),系统是通过isa - swizzling来实现KVO的,参见官方文档

参考并整理自:

iOS开发by唐巧(微信公众号):iOS面试题

内容概要:本文详细介绍了一个基于Java和Vue的联邦学习隐私保护推荐系统的设计与实现。系统采用联邦学习架构,使用户数据在本地完成模型训练,仅上传加密后的模型参数或梯度,通过中心服务器进行联邦平均聚合,从而实现数据隐私保护与协同建模的双重目标。项目涵盖完整的系统架构设计,包括本地模型训练、中心参数聚合、安全通信、前后端解耦、推荐算法插件化等模块,并结合差分隐私与同态加密等技术强化安全性。同时,系统通过Vue前端实现用户行为采集与个性化推荐展示,Java后端支撑高并发服务与日志处理,形成“本地训练—参数上传—全局聚合—模型下发—个性化微调”的完整闭环。文中还提供了关键模块的代码示例,如特征提取、模型聚合、加密上传等,增强了项目的可实施性与工程参考价值。 适合人群:具备一定Java和Vue开发基础,熟悉Spring Boot、RESTful API、分布式系统或机器学习相关技术,从事推荐系统、隐私计算或全栈开发方向的研发人员。 使用场景及目标:①学习联邦学习在推荐系统中的工程落地方法;②掌握隐私保护机制(如加密传输、差分隐私)与模型聚合技术的集成;③构建高安全、可扩展的分布式推荐系统原型;④实现前后端协同的个性化推荐闭环系统。 阅读建议:建议结合代码示例深入理解联邦学习流程,重点关注本地训练与全局聚合的协同逻辑,同时可基于项目架构进行算法替换与功能扩展,适用于科研验证与工业级系统原型开发。
源码来自:https://pan.quark.cn/s/a4b39357ea24 遗传算法 - 简书 遗传算法的理论是根据达尔文进化论而设计出来的算法: 人类是朝着好的方向(最优解)进化,进化过程中,会自动选择优良基因,淘汰劣等基因。 遗传算法(英语:genetic algorithm (GA) )是计算数学中用于解决最佳化的搜索算法,是进化算法的一种。 进化算法最初是借鉴了进化生物学中的一些现象而发展起来的,这些现象包括遗传、突变、自然选择、杂交等。 搜索算法的共同特征为: 首先组成一组候选解 依据某些适应性条件测算这些候选解的适应度 根据适应度保留某些候选解,放弃其他候选解 对保留的候选解进行某些操作,生成新的候选解 遗传算法流程 遗传算法的一般步骤 my_fitness函数 评估每条染色体所对应个体的适应度 升序排列适应度评估值,选出 前 parent_number 个 个体作为 待选 parent 种群(适应度函数的值越小越好) 从 待选 parent 种群 中随机选择 2 个个体作为父方和母方。 抽取父母双方的染色体,进行交叉,产生 2 个子代。 (交叉概率) 对子代(parent + 生成的 child)的染色体进行变异。 (变异概率) 重复3,4,5步骤,直到新种群(parentnumber + childnumber)的产生。 循环以上步骤直至找到满意的解。 名词解释 交叉概率:两个个体进行交配的概率。 例如,交配概率为0.8,则80%的“夫妻”会生育后代。 变异概率:所有的基因中发生变异的占总体的比例。 GA函数 适应度函数 适应度函数由解决的问题决定。 举一个平方和的例子。 简单的平方和问题 求函数的最小值,其中每个变量的取值区间都是 [-1, ...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值