1,我们为什么要启用变量
因为我也不确定用户扔给我的数据是什么,有些同学在写程序的时候就已经将变量限定死了,我就说你这样不好,你只想测试一次吗,如果你想确保你的程序万无一失,通俗点说的话,就是“抗揍”,你就要不断的击打它。对于程序,你就要输入不同的数据类型,去测试它。
这里就涉及到变量的绑定,你输入进来的任何的东西我们都可以用一个变量绑定,然后我在编写程序的过程中就可以直接用变量来代替,可能在程序的运行过程中需要创建多个变量,或者多个变量引用一块内存数据。
那么这块内存数据什么时候会被释放呢,这个时候我们要用到引用计数的概念,每减少一个变量,它的引用计数就会减一,为什么要引入对象引用计数的概念,是因为解决内存泄漏问题,当引用计数为零的时候,系统就知道该对象应销毁了,如果此刻对象的引用计数不为零,那么就不能回收这部分资源。
2,引用计数是什么鬼?
Python的GC模块主要运用了引用计数来跟踪和回收垃圾。在引用计数的基础上,还可以通过“标记-清除”解决容器对象可能产生的循环引用的问题。
引用计数有一个很大的优点,即实时性,任何内存,一旦没有指向它的引用,就会被立即回收,而其他的垃圾收集技术必须在某种特殊条件下才能进行无效内存的回收。
通过分代回收以空间换取时间进一步提高垃圾回收的效率。
分代回收它是根据对象存活的生命周期来进行划分的,分为老年代和新生代,老年代的特点是每次回收只有少量对象被回收,而新生代是每次回收都会有更多的对象被回收,
所有新生成的对象都是被划分在新生代中,新生代的目标就是尽可能快速的收集掉那些生命周期短的对象。
因为新产生的对象比较多,新生代使用复制算法,当Minor GC时如果存活对象过多,无法完全放入Survivor区,就会向老年代借用内存存放对象,以完成Minor GC。
可达性分析法类似于树形结构,是用来判断对象是否还存在引用,对于用可达性分析法搜索不到的对象,GC并不一定会回收该对象。
当对象在新生代躲过一次Minor GC的话,其对象年龄便会加1,默认情况下,如果对象年龄达到15岁,就会移动到老年代中。
一般来说,大对象会被直接分配到老年代,所谓的大对象是指需要大量连续存储空间的对象,最常见的一种大对象就是大数组或者长字符串。
而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法,这个算法不详细说了,大家对算法感兴趣可以自行百度了解。
3,存在一个永久代
注意,在堆区之外还有一个代就是永久代(Permanet Generation或者说新的metaspace),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类,常量知道吧,就是程序运行过程中不会改变的量。
如果在开发时进行内存泄露检查;在发布时能够确保不会内存泄露,那么就可以延长 Python 的垃圾回收时间间隔、甚至主动关闭垃圾回收机制,从而提高运行效率。
因为运用这种垃圾回收机制也有它的缺点,引用计数机制所带来的维护引用计数的额外操作与Python运行中所进行的内存分配和释放,引用赋值的次数是成正比的
因为对象之间相互引用,每个对象的引用都不会为0,所以这些对象所占用的内存始终都不会被释放掉。
Python中的循环引用不可能存在于不可变对象当中,更多的存在于容器对象当中,比如list、dict、class等等。这也使得该方法带来的开销只依赖于容器对象的的数量
4 浅拷贝和深拷贝
浅拷贝出现冲突问题,因为通过这个变量可以改变嵌套层的绑定情况,这样这个变量被重复引用就会被改得面目全非。
浅拷贝的操作
深拷贝的操作
其实每一个对象都有自己的引用计数,我们如果要解除绑定,用del进行解绑,按照存在的变量找到大对象,然后根据大对象的索引找到里面的小对象,删除以后就会有对象的引用计数减少一个。
deepcopy()和copy()的综合操作
只要有一个变量不绑定对象,对象就会被回收,列表用加等于改变原来的列表,这样操作以后的列表id仍然保持一致,而元组用加等于创建一个新的元组,所以元组加等于操作前后id会不一样。