1. 内存管理关我屁事?
内存管理是指在程序的运行过程中,分配内容和回收内存的过程。如果只分配,不回收,电脑上那点内存很快就被用光。
好的程序能够高效的使用内存,不好的程序会造成过多的内存消耗,内存泄露,栈溢出,程序死翘翘。
幸运的是,Python和Java等高级语言会自动管理内存的分配和回收。
但程序员仍然必须具有一定内存管理知识!
小白需要内存管理知识避免犯低级错误,高手需要内容管理知识来优化程序性能,就像赛车手调教汽车的性能。
举个例子:生成包含1亿个随机字符串的序列,小白可能用list,而有点经验的会用generator。内存的使用效率可能差了一亿倍。
generator出现的一个重要原因就是省内存。
2. 可变数据类型和不可变数据类型
当我们调用函数的时候,我们需要传递参数,这时候变量从一个函数传递到了另一个函数。这个传递过程发生了什么?
被传递的对象是被复制了一份呢?还是就是同一份呢?
我们得先理解变量的存储结构,尤其是list等容器类的存储。
看下面的代码:
当我们定义了一个变量name = '麦叔',在内存中有两个部分:
一个是name这个变量名
一个是真正存储'麦叔'这个字符串的对象
上面的代码在内存中的过程是这样的:
开始变量名name指向了“麦叔”。
后来变量名name指向了“张三”。
注意:这时候“麦叔”不再有变量使用了,可能会被垃圾回收器销毁掉。
对于一个复杂的数据类型,比如list,原理是相同的但是更复杂:
cities一直指向内存中的列表对象,列表中的值改变不会改变cities在内存中的地址。改变的是列表指向的另外一个对象的地址。
这里的要点:
name等字符串是不可变的,当变量的值变化了,实际上生成了一个新的变量,内存地址变化了。
基本数据类型都是不可变的,还有整数,小数等都是不可变。当一个变量的值发生了变化,实际上是创建了一个新的对象。
而cities是个list,是可变的,里面的值会发生变化,内存地址没有发生变化。可变还包括dict等。
3.参数传递
理解了变量的内存存储结构,来看参数传递的问题,在函数调用过程中,参数传递到底是传递了什么?是复制了一份吗?
简单说:函数参数和返回值的传递都是传递的变量指向的内存地址!
对于普通对象(不可变类型)和容器类变量可能看起来效果不一样,但实际上原理是一样的。如下面的代码:
全局变量name指向张三,这一直都没有变过。
局部变量name本来是指向张三的,但是后来指向了李四,这并不影响全局变量还是指向张三。
对于列表,改变列表的值会影响全局变量。
4. 引用次数和垃圾回收器
上面的知识和内存管理有什么关系?当然有!
内存管理的基本原理:回收掉没用的内存!
怎么判定有用没用呢:如果还有变量在使用就不要回收,没变量使用了就干掉它。
好可怕,做个打工人也一样吧?如果你还有用就继续打工,没用了就干掉。
上面的例子中:'麦叔'这个对象没用了,因为变量name指向了新的对象'张三'。
Python用引用次数,英文叫做reference count来表示有几个变量在使用这个对象。
通过一个叫做垃圾回收器(英文是Garbage Collector)的后台线程定期查看是否有些变量的引用次数为0,并清理掉引用次数为0的对象。
引用次数是如何产生的?
当对象被赋值给新的变量,引用次数就会增加
当变量作为参数传递给其他函数,引用次数就会增加
引用次数如何减少?
当函数执行结束,引用对象的变量不再有效,引用次数减少
我们可以通过sys.getrefcount查看一个对象的引用次数:
上面的代码会打印出4次!
这是怎么回事?明明只有一次啊!这4个引用是:
name变量
getrefcount:当name被传递给getrefcount函数的时候,函数的参数也指向了它。
Python解释器:为了执行这个脚本,Python解释器也保留了一个引用,直到脚本结束。只针对脚本全局变量。
编译优化器:当执行脚本的时候,优化器会尝试优化字节码,所以也产生了一次引用。这个引用是临时的,很快就会消失。
如果不在脚本中运行,直接在交互式Python下运行,refcount是2,因为没有后面两个引用:
再来看一段代码:
因为name2和name3都指向了'麦叔',所以引用次数不断增加。
注意:getrefcount函数执行完,它产生的引用就消失了,所以不会因为调用了3次而增加3个。
5. 手工回收
正常情况下,回收垃圾这事儿都是Python的垃圾回收器的活。
就像赛车手要自己调教汽车,必要的时候我们也可以自己动手。
执行结果如下:
运送垃圾的车有自己的时间点,比如每天早上6点。但如果垃圾太多了,也可以打电话让他们马上来过清理垃圾。
垃圾回收器有它自己的运行节奏。我们可以调用gc.collect()让它马上执行回收操作。
6. 要点
知道可变数据类型,不可变数据类型,以及参数传递原理
理解list等可变数据类型作为参数传递后修改的是同一个对象
合理使用对象,避免占用太多内存,比如大数据情况下使用generator而不是list
避免变量循环引用,造成引用数永远不为零,可能造成不可回收而引起内存泄露。
今天这些对大部分人可能够用了。其实内存管理和垃圾回收是比较高级的话题,属于编程的深水区。有兴趣的建议多研究一下,深水区里去游一下。
7. 你动动手,就能让我乐呵呵!
我是麦叔,如果喜欢本文,可以关注,在看,收藏,如果能转发就让我乐翻天啦!