【python测试开发栈】python内存管理机制(一)—引用计数
什么是内存
在开始进入正题之前,我们先来回忆下,计算机基础原理的知识,为什么需要内存。我们都知道计算机的CPU相当于人类的大脑,其运算速度非常的快,而我们平时写的数据,比如:文档、代码等都是存储在磁盘上的。磁盘的存取速度完全不能匹配cpu的运算速度,因此就需要一个中间层来适配两者的不对等,内存由此而来,内存的存取速率很快,但是存储空间不大。
举一个图书馆的例子,便于大家理解,我们图书馆的书架就相当于磁盘,存放了大量的图书可以供我们阅读,但是如果书放在书架上,我们没办法直接阅读(效率低),只能将书取出来,放在书桌上看,那书桌就相当于内存。
内存回收
内存资源毕竟是有限的,所以在使用之后,必须被回收掉,否则系统运行一段时间后就会因无内存可用而瘫痪。我们软件测试领域常用的两种语言:java和python,全部都采用内存自动回收的方法,也就是我们只管申请内存,但是不管释放内存,由jvm和python解释器来定期触发内存回收。作为对比,C语言和C++中,程序员需要使用malloc申请内存,使用free去释放内存,malloc和free必须成对的出现,否则非常容易出现内存问题。
还拿上面图书馆的例子,假如图书馆的书看完之后放在书桌上就可以(因为图书可自动回收),那么很快的,就没有位置给新进来的同学看书了。这时候就需要图书馆管理员(jvm或python解释器)定期的回收图书,清空书桌。不过正常情况下,我们离开图书馆时,要自己清空书桌,将书放回书架(类似C语言和C++的内存回收方式)。
python内存管理
引用计数
python通过引用计数来进行内存管理,每一个python对象,都维护了一个指向该对象的引用计数。python的sys库提供了getrefcount()函数来获取对象的引用计数。下面我们看个例子(注意:不同版本的python,运行结果不同,我这里采用的是python3.7.4):
"""
@author: xuanke
@time: 2019/11/27
@function: 测试python内存
"""
import sys
class RefClass(object):
def __init__(self):
print("this is init")
def ref_count_test():
# 验证普通字符串
str1 = "abc"
print(sys.getrefcount(str1))
# 验证稍微复杂点的字符串
print(sys.getrefcount("xuankeTester"))
# 验证小的数字
print(sys.getrefcount(12))
# 验证大的数字
print(sys.getrefcount(257))
# 验证类
a = RefClass()
print(sys.getrefcount(a))
# 验证引用计数增加
b = a
print(sys.getrefcount(a))
# 验证引用计数减少
b = None
print(sys.getrefcount(a))
if __name__ == '__main__':
ref_count_test()
大家先来思考下,最终的结果会是什么?!我觉得应该很多人都会答错,因为不同版本的python,对引用变量个数有影响(主要是可复用的对象)。我们先贴出来运行结果,再来分析产生结果的原因:
27
4
9
3
this is init
2
3
2
不过提前声明一点:sys.getrefcount函数在使用时,因为将对象(比如上例中的str1)作为参数传入,所以会额外增加一个变量(相当于getrefcount持有了str1的引用),因此实际每个对象的实际引用计数都得减1。下面分别介绍下上面的几种情况:
- 字符串: str1='abc'的引用数是27-1=26,是因为字符串'abc'比较简单,在python解释器(CPython)中确实可能存在26个引用。作为对比,在python2.7中,str1的引用变量个数是3-1=2。而字符串'xuanketester',是我自定义的一个字符串,所以不可能会有其他额外的引用,所以其引用变量个数是3-1=2(至于为什么是2,理论应该是0,是因为python解释器默认持有了所有字符串的两个引用)。
- 数字: 数字12对应的引用计数个数是9-1=8,而257对应的引用计数个数是3-1=2,这主要是因为,在python初始化过程中,就创建了从-5到256的数字,缓存起来,这样做是为了频繁的分配内存,提高效率。而对于不在这个区间的数字,则会重新分配内存空间。所以数字12因为被复用,其引用计数个数是8(在python2.7.14中,其引用计数个数是8)。
- 类: 在上面例子中,创建一个RefClass对象,其引用计数就是2-1=1,因为其是一个我们自定义的类对象,在python解释器(Cpython)中肯定不会被复用。
我们可以通过打印内存地址的方式来验证上面这几种情况:
def memory_address_test():
str1 = 'xuankeTester'
str2 = 'xuankeTester'
print(id(str1))
print(id(str2))
str3 = 'abc'
str4 = 'abc'
print(id(str3))
print(id(str4))
a = 12
b = 12
print(id(a))
print(id(b))
c = 257
d = 257
print(id(c))
print(id(d))
按照我们上面的分析,c和d的地址应该是不一样的,a和b的地址是一样的,字符串str1和str2、str3和str4内存地址都是一样的。但是我在pycharm中,直接运行py文件,结果却和预想的不一致,结果如下:
2854496960176
2854496960176
2854496857840
2854496857840
140724423258720
140724423258720
2854498931120
2854498931120
所有情况的内存地址都是一样的,这是为什么呢?我考虑到是不是pycharm对py文件做了优化,于是我又在命令行尝试执行,结果还是一样的。所以,我猜测可能是python解释器在执行文件时,为了提高py文件的执行效率,对文件的内存地址做了优化—相同内容的对象内存地址都一样。
为了验证这个想法,我直接在python交互模式下执行,果然得到了我想要的结果:
Python 3.7.4 (tags/v3.7.4:e09359112e, Jul 8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> a=12
>>> b=12
>>> id(a)
140724423258720
>>> id(b)
140724423258720
>>> a=257
>>> b=257
>>> id(a)
2559155778384
>>> id(b)
2559155778192
>>> a='xuankeTester'
>>> b='xuankeTester'
>>> id(a)
2559155711280
>>> id(b)
2559155711280
>>>
从上面可以看到两个257对应的地址确实是不一样的,和我们最初判断的是一致的。
总结
python通过对象的引用计数来管理内存,其实java的JVM也有用引用计数,所以理解了引用计数,为我们理解python的垃圾回收方法打下了基础。本计划这一篇文章就将python内存管理的机制讲完的,但是发现一个内存引用计数就有很多东西得写,所以索性就分两篇文章来写,之后再写一篇文章来介绍python的垃圾回收方式。
【python测试开发栈】python内存管理机制(一)—引用计数的更多相关文章
- cocos2dx中的内存管理机制及引用计数
1.内存管理的两大策略: 谁申请,谁释放原则(类似于,谁污染了内存,最后由谁来清理内存)--------->适用于过程性函数 引用计数原则(创建时,引用数为1,每引用一次,计数加1,调用结束时, ...
- swift内存管理中的引用计数
在swift中,每一个对象都有生命周期,当生命周期结束会调用deinit()函数进行释放内存空间. 观察这一段代码: class Person{ var name: String var pet: P ...
- 【python测试开发栈】—python内存管理机制(二)—垃圾回收
在上一篇文章中(python 内存管理机制-引用计数)中,我们介绍了python内存管理机制中的引用计数,python正是通过它来有效的管理内存.今天来介绍python的垃圾回收,其主要策略是引用计数 ...
- 【python测试开发栈】python基础语法大盘点
周边很多同学在用python,但是偶尔会发现有人对python的基础语法还不是特别了解,所以帮大家梳理了python的基础语法(文中的介绍以python3为例).如果你已然是python大牛,可以跳过 ...
- python测试开发django-17.admin后台管理
前言 通常一个网站开发,需要有个后台管理功能,比如用后台管理发布文章,添加用户之类的操作.django的admin后台管理主要可以实现以下功能 基于admin模块,可以实现类似数据库客户端的功能,对数 ...
- OC基础15:内存管理和自动引用计数
"OC基础"这个分类的文章是我在自学Stephen G.Kochan的<Objective-C程序设计第6版>过程中的笔记. 1.什么是ARC? (1).ARC全名为A ...
- Linux内存管理 (11)page引用计数
专题:Linux内存管理专题 关键词:struct page._count._mapcount.PG_locked/PG_referenced/PG_active/PG_dirty等. Linux的内 ...
- 【python测试开发栈】带你彻底搞明白python3编码原理
在之前的文章中,我们介绍过编码格式的发展史:[文章传送门-todo].今天我们通过几个例子,来彻底搞清楚python3中的编码格式原理,这样你之后写python脚本时碰到编码问题,才能有章可循. 我们 ...
- 【python测试开发栈】—理解python深拷贝与浅拷贝的区别
内存的浅拷贝和深拷贝是面试时经常被问到的问题,如果不能理解其本质原理,有可能会答非所问,给面试官留下不好的印象.另外,理解浅拷贝和深拷贝的原理,还可以帮助我们理解Python内存机制.这篇文章将会通过 ...
随机推荐
- uni-app 请求封装
1.创建一个http.js const baseUrl = 'http://192.168.1.188:8080'; const httpRequest = (opts, data) => ...
- Apache POI使用指南(HSSFWorkbook生成excel)
说 明: 官网:http://poi.apache.org/ 由于poi的功能多样,可以生成ppt.word.excel.......,本文就以生成excel为例进行说明,相信聪明的你一定能举一反三 ...
- django-MVT设计模式
MVT:Models.Views.Templates Model:封装数据库,对数据库进行访问,对数据进行增删查改等. View:业务逻辑的一些操作. Templates:展示. 而MVC主要的流程如 ...
- Java面向对象的三大特征和五大原则
Java面向对象的三大特征 封装 封装(Encapsulation)是指属性私有化,根据需要提供setter和getter方法来访问属性.即隐藏具体属性和实现细节,仅对外开放接口,控制程序中属性的访问 ...
- windows vscode 远程调试代码
需要: vscode + Remote-ssh(vscode插件中下载) openssh (https://www.mls-software.com/files/setupssh-8.0p1-2.ex ...
- xss代码集
</script>"><script>prompt(1)</script> </ScRiPt>"><ScRiPt& ...
- [2018-06-28] django项目 实例
实例一.显示一个基本的字符串在网页中 首先先进入views.py def home(request): string = u'随便写' return render(request, 'home.htm ...
- 拼多多后台开发面试真题:如何用Redis统计独立用户访问量
众所周至,拼多多的待遇也是高的可怕,在挖人方面也是不遗余力,对于一些工作3年的开发,稍微优秀一点的,都给到30K的Offer,当然,拼多多加班也是出名的,一周上6天班是常态,每天工作时间基本都是超过1 ...
- 使用Typescript重构axios(九)——异常处理:基础版
0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...
- P2579 [ZJOI2005]沼泽鳄鱼(邻接矩阵,快速幂)
题目简洁明了(一点都不好伐) 照例,化简题目 给一张图,每一个时间点有一些点不能走,(有周期性),求从起点第k秒恰好在终点的方案数,可重复,不可停留. 额dp实锤 于是就被打脸了.... 有一种东西叫 ...