什么是内存

在开始进入正题之前,我们先来回忆下,计算机基础原理的知识,为什么需要内存。我们都知道计算机的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内存管理机制(一)—引用计数的更多相关文章

  1. cocos2dx中的内存管理机制及引用计数

    1.内存管理的两大策略: 谁申请,谁释放原则(类似于,谁污染了内存,最后由谁来清理内存)--------->适用于过程性函数 引用计数原则(创建时,引用数为1,每引用一次,计数加1,调用结束时, ...

  2. swift内存管理中的引用计数

    在swift中,每一个对象都有生命周期,当生命周期结束会调用deinit()函数进行释放内存空间. 观察这一段代码: class Person{ var name: String var pet: P ...

  3. 【python测试开发栈】—python内存管理机制(二)—垃圾回收

    在上一篇文章中(python 内存管理机制-引用计数)中,我们介绍了python内存管理机制中的引用计数,python正是通过它来有效的管理内存.今天来介绍python的垃圾回收,其主要策略是引用计数 ...

  4. 【python测试开发栈】python基础语法大盘点

    周边很多同学在用python,但是偶尔会发现有人对python的基础语法还不是特别了解,所以帮大家梳理了python的基础语法(文中的介绍以python3为例).如果你已然是python大牛,可以跳过 ...

  5. python测试开发django-17.admin后台管理

    前言 通常一个网站开发,需要有个后台管理功能,比如用后台管理发布文章,添加用户之类的操作.django的admin后台管理主要可以实现以下功能 基于admin模块,可以实现类似数据库客户端的功能,对数 ...

  6. OC基础15:内存管理和自动引用计数

    "OC基础"这个分类的文章是我在自学Stephen G.Kochan的<Objective-C程序设计第6版>过程中的笔记. 1.什么是ARC? (1).ARC全名为A ...

  7. Linux内存管理 (11)page引用计数

    专题:Linux内存管理专题 关键词:struct page._count._mapcount.PG_locked/PG_referenced/PG_active/PG_dirty等. Linux的内 ...

  8. 【python测试开发栈】带你彻底搞明白python3编码原理

    在之前的文章中,我们介绍过编码格式的发展史:[文章传送门-todo].今天我们通过几个例子,来彻底搞清楚python3中的编码格式原理,这样你之后写python脚本时碰到编码问题,才能有章可循. 我们 ...

  9. 【python测试开发栈】—理解python深拷贝与浅拷贝的区别

    内存的浅拷贝和深拷贝是面试时经常被问到的问题,如果不能理解其本质原理,有可能会答非所问,给面试官留下不好的印象.另外,理解浅拷贝和深拷贝的原理,还可以帮助我们理解Python内存机制.这篇文章将会通过 ...

随机推荐

  1. wpf 依赖强制回调

    依赖属性的强制转换加回调,后期自己再改 using System; using System.Collections.Generic; using System.Diagnostics; using ...

  2. SQL common keywords examples and tricks

    Case Sensitive Check 1. Return names contain upper case Select id, name from A where name<>low ...

  3. C# WPF基础巩固

    时间如流水,只能流去不流回. 学历代表你的过去,能力代表你的现在,学习能力代表你的将来. 学无止境,精益求精. 一.写作目的 做C# WPF开发,无论是工作中即将使用,还是只应付跳槽面试,开发基础是非 ...

  4. WebApi HttpUtils

    public class HttpUtils { private static final String TAG = "uploadFile"; * ;//超时时间 /** * 通 ...

  5. SteamVR Plugin

    使用HTC vive基于unity做虚拟现实,需要用到steamVR插件,最近查找了很多资料,稍微做一下总结. 做虚拟现实无非是头显在场景中的camera功能以及手柄的操作功能. (一)camera以 ...

  6. java script三大组成部分

    JavaScript是一种属于网络的脚本语言,已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果.通常JavaScript脚本是通过嵌入在HTML中来实现 ...

  7. sqlite复制表

    (1)复制表,并把原表的 所有记录都复制到新表里. CREATE TABLE newTb AS SELECT * FROM oldTb (2)只复制表结构,不复制数据到新表里. 注:该语句无法复制关键 ...

  8. (Java) JWT-TokenUtils

    package com.vcgeek.hephaestus.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; imp ...

  9. FTP上传、下载文件Demo

    前言:最近在做一个app,负责写后台接口,客户那边给了一个FTP的账号密码过来,服务器上面放了一堆的PDF文件,让我们这边自己从上面拿,项目是spriongboot的,做个记录供以后参考. 一.app ...

  10. 爬虫之CrawlSpider简单案例之读书网

    项目名py文件下 class DsSpider(CrawlSpider): name = 'ds' allowed_domains = ['dushu.com'] start_urls = ['htt ...