今天刚学习了内存区相关的知识,前来梳理总结一下!!!
1. 内存简析
1.1内存区的分析
软件程序在计算机中的执行,主要是通过数据单元、控制单元、执行单元共同协作,完成数
据的交互,达到程序处理数据的目的,在软件执行的过程中,由于系统内存和 CPU 的资源非常有限,所以有效的分解软件中的各项数据,将不同的数据加载到不同的内存部分以有效的运行程序,同时可以达到在一个计算机中有效 运行更多软件的目的
1.2python内存区的区别
Python 程序在运行过程中,主要是解释器从系统中申请内存空间已运行python软件
解释器将申请的内存主要区分为几个内存区
栈内存区:
用于直接分配数据,存取速度较快,数据存储不稳定小数据,,比如变量
方法区:
主要用于加载数据中使用的代码数据,二进制数据,方法数据等等程序需要预加载的数据
静态区:
主要用于加载数据中的一些静态数据,常量数据等,在python 中的不可变数据类型的数据,也会存储在静态区中
堆内存:
存储稳定持久,一般用于存储加载较为重量级的数据,如程序运行过程中的对象都是存在堆内存中的
1.3不可变数据类型 /可变数据类型
Python中根据数据是否可以进行修改提供了两种不同的数据类型
一种是不可变数据类型:比如基本数据类型都是不可变数据类型,如整型,字符串
另一种是可变数据类型,一般组合数据类型或者自定义数据类都是可变数据类型
可变和不可变的区分:
Python 中的一切都是对象,可以通过id()函数查询对象在内存中的ip地址数据
可变数据类型是在定义了数据之后,修改变量的数据,原来的内存地址不变
不可变数据类型,在定义数据之后,修改变量的数据,变量不会修改原来内存地址而是会指向新的地址,原有的数据保留,提高数据的利用率
案例:
可变类型
a = list()#堆内存中:存在一个对象list(),一个变量a指向这个对象
Print(id(a))#查看对象a的内存
Print(a)
不可变类型
整数类型:-5~256:在解释器加载时,已经自动分配了这些数字的内存
超出-5~256范围的整数,在一个代码块中申请一次内存
交互模式:一行命令就是一个代码块
ide模式~工具开发:一个模块就是一个代码块
案例:
b = 12
Print(id(b))
b =13
Print(id(b))
内存地址不同
案例2
a =1000
b =1000
Print(id(a))
Print(id(b))
不可变数据类型
内存地址相同
#思考题
Nums = [12,13,15,18,20,”hello”,[“world”,”python”]]
a = 20#nums[4]是否同一个内存地址?是
print(id(a), id(nums[4]))
b = "hello"# nums[5] 是否同一个内存地址?是
print(id(b), id(nums[5]))
c = ["world", "python"] # nums[6] 是否同一个内存地址不是
print(id(c), id(nums[6]))
奇怪的语法
if [1][0]: #意思是判断列表[1]的0号下标是否为True:1为True,正确
Print(“hello”)
Else:
Print(“world”)
不可变类型,在同一个代码块中进行多次使用时
会将该类型的对象,直接创建在常量区,在任意引用时候的时候直接赋值内存地址因为不可变类型的对象数据不会发生变化,所以内存中存储一份即可!优化程序执行效率
一般情况下,可变类型的对象会创建在堆内存中;不可变类型的对象会创建在常量区内存中
4. 代码和代码块
Python中的最小运行单元是代码块,代码块的最小单元是一行代码
在实际开发过程中,需要注意的是python有两种操作方式
交互模式
Ide 开发模式
在交互模式下,每行命令是一个独立运行的代码块,每个代码块运行会独立申请一次内存,
在操作过程中交互模式没有退出的情况下遵循 PYTHON 官方操作标准
但是在 IDE 开发模式下,代码封装在模块中,通过 python 命令运行模块时,模块整体作为一个代码块向系统申请内存并执行程序,执行过程中对于基本数据类型进行缓存优化操作
5. 代码内存测试
使用模块memory_profile来测试内存
通过@profile注解,让模块可以直接进行检测
最小注解代码单元为函数和对象
6. 操作符号:is 和 == 的使用
如何判断对象和对象之间的关系那
Python 提供了对象操作符号is 和内容操作符号==,用于判断对
象和对象中的值的情况
A is B:判断对象 A 和对象 B 是否同一个内存地址,即是否同一个对象
A == B:判断 A 中的内容是否和 B 中的内容一致
不论是基本类型的数据,还是内容复杂的对象,都可以通过对象判断符号 is 和内容判断操作符号==来进行确定
isInstance 判断是不是同一个类型
组合数据类型的数据判断
创建的每个组合数据类型的对象都是独立的,如下面的代码中的 a 和 b 变量中分别存放了
两个不同的列表类型的对象,所以 is 判断是 False,但是值又是相同的所以==判断 True
7. 引用、浅拷贝、深拷贝
关于对象的内存分配
创建对象,依赖于申请的内存空间中数据的加载。。对象在内存中的创建过程依赖于三部分
内存处理:对象分配内存地址、引用变量分配内存地址、对象和引用变量之间的关联
内存分解
问题:在程序中如果要在多个地方使用一个对象数据时应该怎么办呢?
PYTHON 中对于这样的情况,有三种不同的操作方式
⚫ 如果程序中多个不同的地方都要使用同一个对象,通过对象的引用赋值,将同一个对象
赋值给多个变量
⚫ 如果程序中多个不同的地方都要使用相同的对象数据,通过对象的拷贝完成数据的简单
复制即可,对象中的包含的数据要求必须统一
⚫ 如果程序中多个不同的地方使用相同的而且独立的对象数据,通过对象的深层次的复制
将对象的数据完整复制成独立的另一份即可
7.1对象的引用赋值
对象的引用赋值,可以将对象的内存地址同时赋值给多个变量,这多个变量中存放的都是同
一个对象的引用地址,如果通过一个变量修改了对象内容,那么其他变量指向的对象内容也
会同步发生改变
将一个变量中存放的对象的地址数据,赋值给其他变量,通过赋值操作符号就可以完成
引用赋值,针对的是可变类型不论是组合数据类型或者自定义 class 类型,都具备引用赋值的操作;
7.2对象的浅拷贝
浅拷贝是对对象的一种临时备份,核心机制主要是对赋值对象内部数据的引用
Python内建标准模块copy提供了函数
使用
A =[1100,2200]
B =copy.copy(A)
指向对象不同,但是指向的列表相同,引用的列表相同
修改A指向列表中的数据b也会发生变化
7.3对象的深拷贝
和对象的浅拷贝不同,对象的深拷贝,是对象数据的直接拷贝,而不是简单的引用拷贝
主要是通过 PYTHON 内建标准模块 copy 提供的 deepcopy 函数可以完成对象深拷贝
使用:
A =[1100,2200]
B =copy.deepcopy(A)
指向的对象不同,指向的列表对象也不同,深拷贝创建了一个新的列表
而且如果修改数据,无论那个数据被修改都不会影响对方数据中的数据
8. 垃圾回收机制
垃圾回收机制(Garbage Collection:GC)基本是所有高级语言的标准配置之一了
在一定程度上,能优化编程语言的数据处理效率和提高编程软件开发软件的安全性能
在 PYTHON 中的垃圾回收机制主要是以引用计数为主要手段
以标记清除和隔代回收机制作为辅助操作手段
完成对内存中无效数据的自动管理操作的
8.1引用计数
引用计数[Reference Counting:RC]是 PYTHON 中的垃圾回收机制的核心操作算法
该算法最早是 George E.Collins 在 1960 年首次提出的,并在大部分高级语言中沿用至今,是很多高级语言的垃圾回收核心算法之一
(1) 什么是引用计数
引用计数算法的核心思想是:当一个对象被创建或者拷贝时,引用计数就会+1,当这个对象的多个引用变量,被销毁一个时该对象的引用计数就会-1,如果一个对象的引用计数为 0 则表示该对象已经不被引用,就可以让垃圾回收机制进行清除并释放该对象占有的内存空间了。
引用计数算法的优点是:操作简单,实时性能优秀,能在最短的时间获得并运算对象引用数
(2) PYTHON 中的引用计数
PYTHON是一个面向对象的弱类型语言,所有的对象都是直接或者间接继承自object类型,
object 类型的核心其实就是一个结构体对象
在结构体中,ob_refcnt 就是对象的引用计数,当对象被创建或者拷贝时该计数就会增加+1,当对象的引用变量被删除时,该计数就会减少-1,当引用计数为 0 时,对象数据就会被回收释放了。在 python 中,可以通过 sys.getrefcount()来获取一个对象的引用
计数
8.2标记清除
PYTHON 中的标记-清除机制主要是针对可能产生循环引用的对象进行的检测机制
在 PYTHON 中的基本不可变类型如 PyIntObject,PyStringObject 等对象的内部不会内聚其他对象的引用,所以不会产生循环引用,一般情况下循环引用总是发生在其他可变对象的内部属性中,如 list,dict,class 等等,使得该方法消耗的资源和程序中可变对象的数
量息息相关!
标记清除算法核心思想:首先找到 PYTHON 中的一批根节点对象,如 object 对象,通过根节点对象可以找到他们指向的子节点对象,如果搜索过程中有这个指向是从上往下的指向,
表示这个对象是可达的,否则该对象是不可达的,可达部分的对象在程序中需要保留下来,不可达部分的对象在程序中是不需要保留的
如果代码中执行了
del a
del b
我们会发现,对象 A()和对象 B()依然有引用指向他们,如果是之前的引用计数的方式明显区分不了这样的对象是否应该删除;但是标记-清除的方式,就可以标记出来对象 A()和对象 B()是不可达对象,不需要保留,直接删除即可!
8.3. 分代回收
PYTHON 中的分代回收机制,是一种通过空间换取时间效率的做法,PYTHON 内部处理机制
定义了三个不同的链表数据结构[第零代(年轻代),第 1 代(中年代),第 2 代(老年代)]
PYTHON 为了提高程序执行效率,将垃圾回收机制进行了阈值限定,0 代链表中的垃圾回收机制执行最为密集,其次是 1 代,最后是 2 代;
PYTHON 定义的这三个链表,主要是针对我们在程序中创建的对象,首先会添加到 0 代链表随后 0 代链表数量达到一定的阈值之后,触发 GC 算法机制,对 0 代对象进行符合规则的引用计数运算,避免出现对象的延迟或者过早的释放,最终,触发 GC 机制将已经没有引用指向的对象进行回收,并将有引用继续指向的对象移动到第 1 代对象链表中;第 1 代对象链表的对象,就是比第 0 代对象链表中的对象可能存活更久的对象,GC 阈值更大检测频率更慢,以提高程序执行效率以此类推直到一部分对象存活在第 2 代对象链表中,对象周期较长的可能跟程序的生命周期一样了