python和ruby的内存机制

垃圾回收机制(Garbage Collection),是自动检测和释放不在需要的内存,由程序来帮我们做内存的管理。

CRuby的内存机制

ruby中所有的变量和常量都仅仅是对象的引用。

内存结构

在这里插入图片描述
在代码执行之前,ruby会提前在堆中创建对象池,所使用的ruby对象都是从这个池子中取出来的,对象池由很多堆页 (page) 构成的,每一个页的大小为 16Kb。 每页中包含 408 个槽 (slot)。 一个槽对应一个对象,每一个槽中放的是一个RVALUE结构体。

typedef struct RVALUE {
    union {
        struct {
            unsigned long flags;   /* 0 if not used */
            struct RVALUE *next;
        } free;
        struct RBasic  basic;
        struct RObject object;
        struct RClass  klass;
        struct RFloat  flonum;
        struct RString string;
        struct RArray  array;
        ...
    } as;
} RVALUE;

一个RVALUE结构体表达了大多数 Ruby 对象,对于对象本身比较小时,可以直接镶嵌倒结构体中存储。对于大对象会在堆中申请一块大内存进行存储数据,在RVAKUE结构体中存放这块内存的指针。
ruby倾向于把申请到的内存在利用,而不是还给操作系统。

垃圾回收

最开始的清除方式-标记清除

当我们从对象池中可用对象已经全部分配出去,没有可用对象时,ruby就会暂停程序,执行GC。
标记:

  • 从根对象开始,遍历所有的对象,将有用的对象进行标记,修改RVALUE结构体的值。
    清除
  • 将所有未被标记的对象清除后,程序继续执行。

一次清理所有的未被标记的对象,会导致程序暂停的时间很长,增加了延迟清除算法,不一次性清除所有无用的对象,将清除的过程分散到后面的操作中,这大大降低了单次清除过程中的中断时间

2.0中标记清除

ruby的COW(copy on write)特性:当存在父子进程时,子进程可以共享父进程的数据,只有当父子进程中任意一个修改共享内存的数据时,会触发复制。

标记过程中,维护相应的位图,将对象是否有用的信息映射到位图中,不再使用RVALUE的flags来表示对象是否在使用。将对象是否有用的信息映射到bitmap中。标记对象的时候,就不会去修改对象的数据,只会修改位图,这样就只会触发复制位图的数据,不会影响到其他 Ruby 对象。

分代回收

在ruby中很多对象都是临时的,也会有永久或时间长的对象,在标记时每次都会去遍历所有根对象,包括对长期存在的对象,有点浪费时间。

分代回收,将对象分为两种:

  • 新生对象
  • 成熟对象

所有的新生对象,在经历了第一阶段的标记清除后,会将剩下的标记对象改为成熟代,ruby的大部分时间都放在遍历新生代的对象,不会重复遍历成熟代的对象,偶尔会进行一次全局回收,对新生代和成熟代的所有对象进行遍历回收。

ruby通过监控成熟代对象的数目来确定何时运行全局回收,当成熟代的数目双倍于上次全局回收的数目时,ruby会清理所有的标记,并且将所有的对象视为新对象。

增量 GC加三色及写屏障

在进行一次全局回收时,还是比较花费时间的。
会对全局回收进行拆分成小任务,

  1. 所有对象刚开始都是白色
  2. 直接引用了其他对象的为黑色,被引用的是灰色, 灰色会被追加到一个队列中。
  3. 依次取灰色对象 (刚开始时没有灰色对象就取根对象) 进行标记,将其直接或间接引用的对象也标记为灰色,到末尾时将自己修改为黑色 (也从队列中剔除)。当所有对象中只有白色和黑色时,标记完成。
  4. 直接将白色对象清除掉,黑色是被需要的对象,清除完成。

过程中出现了已标记对象引用了新对象,也就是黑色对象引用了白色对象,那么写屏障会检测到这种情况,并将该黑色对象修改为灰色对象,这个对象也就会重新走上面的第 3 步。

Cpython的内存机制

内存结构

python的内存结构分为:栈,堆,静态存储区,数据区。

python创建一个对象时s="123",会立即向操作系统请求内存,将"123"这个值放到申请的内存中,在将变量名和这个内存地址进行绑定,然后将其引用计数设置为1。

垃圾回收

引用计数

在每次申请内存的时候,都会留一部分空间来处理引用计数,当有其他变量绑定到这块内存地址时,其引用计数进行加1,当已绑定的变量解除绑定时引用计数减1,当这块内存的引用计数为0时,立即对其进行清除,将内存还给操作系统。

标记清除

但是会存在引用计数无法为0的情况,就是两个数组循环引用

a = [1,2]
b = [3,4]

a.append(b)
b.append(a)

在内存中开辟两两个数组对象,数组a中存储的是对象1的内存地址,对象2的内存地址,数组b的内存地址,数组b中存储的是对象3的内存地址,对象4的内存地址,数组a的内存地址。

此时内存中的存储和引用计数为:
在这里插入图片描述
当变量a和变量b与内存的绑定关系解除时,数组a和数组b的引用计数减少到1,申请的几个内存的引用计数都是1,python使用引用计数的方式无法将这几块内存进行释放,还给操作系统。

所以需要对循环引用进行检测,python采用的是标记清除,从根开始,即从变量名开始,可以直接引用或者间接引用到的内存地址进行标记,标记完成后,对没有标记的进行清除,并将内存还给操作系统。

分代回收

每次标记清除的时候,会从根开始遍历,遍历所有的对象,这样耗时比较久,对于存在时间久的对象来说会多次重复的遍历,基于对象存在时间越长,越不可能是垃圾的思想,对对象进行分代遍历

  • 新生代
  • 青年代
  • 老年代

每次标记清除都会遍历新生代的对象,没有被清除的对象,都变为青年带,当新生代的遍历次数达到一定次数时,会遍历青年代的对象,青年代没有被清除的变为老年代,当青年代的标记清除次数达到一定数量,遍历老年代进行标记清除。

小整数池

对于小整数的定义范围是[-5,256],这些对象是python解析器提前生成好的对象,永远不会被垃圾回收机制回收,避免了创建相同的小整数带来的重复申请内存空间的效率问题。

python和ruby在内存机制上的对比

  1. python是创建对象时,向操作系统申请分配内存,清除对象时立马将内存还给操作系统,程序在执行过程中所占用的内存少,ruby是创建一个对象池,创建对象时从对象池中拿,清除对象时将对象还给对象池,用户自定义对象会重新申请内存,对象池中会拿出一个对象来存储新申请内存的地址,而且ruby不会将申请到的内存返还给操作系统,在运行时,程序所占内存空间可能会很大。

  2. python使用引用计数,每个对象都需要额外的空间处理引用计数,每个简单的操作都会变成一个复杂的操作,如修改变量的引用,引用计数会修改,内存会释放,当一个一个包含了很多元素的列表引用计数为0时,就会一次性释放大量的对象,导致时间久。ruby会在标记完后,执行清除,将清除分为小任务,减少了清除过程中的中断操作,一次清除占用时间少。

  3. python的分代回收分为3代,新生代、青年代、老年代。ruby的分代回收分为两代,新生代、成熟代。

  4. python中没有常量的概念,只是人为一个规定,规定全大写的为常量,但是是可以修改其值的,如果常量名与其他内存绑定了,这块内存是会释放还给操作系统。ruby的静态变量、全局变量、modules、classes不会被垃圾机制收集。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值