首页 > 系统相关 >内存管理

内存管理

时间:2023-08-13 09:00:45浏览次数:46  
标签:管理 对象 内存 回收 引用 泄露 垃圾

内存管理

python——内存管理

python的内存管理机制:引用计数、垃圾回收,内存池机制

接口:

gc.disable()  # 暂停自动垃圾回收.
gc.collect()  # 执行一次完整的垃圾回收, 返回垃圾回收所找到无法到达的对象的数量.
gc.set_threshold()  # 设置Python垃圾回收的阈值.
gc.set_debug()  # 设置垃圾回收的调试标记. 调试信息会被写入std.err.

一、变量与对象

关系图:

img

  1. python中万物皆对象,所以python的存储问题是对象的存储问题,并且对于每个对象,python会分配一块内存空间去存储它。
  2. 对于整数和短小的字符等,python会执行缓存机制,将这些对象进行缓存,不会为相同的对象分配多个内存空间。
  3. 容器对象,如列表、元组、字典等,存储的其他对象,仅仅是其他对象的引用,即地址,并不是这些对象本身。

二、引用计数

在python中,每个对象都有指向该对象的引用总数——称为“引用计数”。

查看对象的引用计数:sys.getrefcount()

计数引用机制:一个对象会记录着引用自己的对象的个数,每增加一个引用,个数加一,每减少一个引用,个数减一。在检测到兑现引用个数为0时,对普通的对象进行释放内存。

引用计数增加:

  • 对象被创建:x=4
  • 另外的变量被创建:y=x
  • 被作为参数传递给函数:foo(x)
  • 作为容器对象的一个元素:a=[1, x, '33']

引用计数减少:

  • 一个本地引用离开了它的作用域。比如上面的foo(x)函数结束时,x指向的对象引用减1。
  • 对象的别名被显示的销毁:del x
  • 对象的一个别名被赋值给其他对象:x=789
  • 对象从一个窗口对象中移除:myList.remove(x)
  • 窗口对象本身被销毁:del myList,或者窗口对象本身离开了作用域

循环引用问题:循环引用即对象之间进行互相引用,出现循环引用后,利用上述引用计数机制无法对循环引用中的对象进行释放空间,这就是循环引用问题。

class Person(object):
    pass
class Dog(object):
    pass
p = Person()
d = Dog()
p.pet = d
d.master = p

这里对象p中的属性引用d,而对象d中属性同时来引用p,从而造成仅仅删除p和d对象,也无法释放其内存空间,因为他们依然在被引用。深入解释就是,循环引用后,p和d被引用个数为2,删除p和d对象后,两者被引用个数变为1,并不是0,而python只有在检查到一个对象的被引用个数为0时,才会自动释放其内存,所以这里无法释放p和d的内存空间。

三、垃圾回收

当python中的对象越来越多,占据越来越大的内存,系统会启动垃圾回收(garbage collection),将没用的对象清除。

垃圾回收的作用:从经过引用计数器机制后还没有被释放掉内存的对象中,找到循环引用对象,并释放掉其内存。

自动回收

当Python运行时,会记录其中分配对象(object allocation)和取消分配对象(object deallocation)的次数。当两者的差值高于某个阈值时,垃圾回收才会启动。

import gc
gc.get_threshold() #gc模块中查看阈值的方法
(700, 10, 10)
阈值分析:700即是系统垃圾回收启动的阈值;每10次0代垃圾回收,会配合1次1代的垃圾回收;而每10次1代的垃圾回收,才会有1次的2代垃圾回收。

手动回收

手动启动垃圾回收gc.collect()

分代回收

  • python将所有的对象分为0、1、2三代;
  • 所有的新建对象都是0代对象;
  • 当某一代对象经历过垃圾回收,依然存活,就被归入下一代对象。

四、内存池机制

Python引用了一个内存池(memory pool)机制,即Pymalloc机制(malloc:n.分配内存),用于对小块内存的申请和释放管理。

Python的内存池分为大内存和小内存:(256K为界限)

大内存使用malloc进行分配
小内存使用内存池进行分配
Python的内存池(金字塔)

  • 第3层:最上层,用户对Python对象的直接操作。
  • 第1层和第2层:内存池,有Python的接口函数PyMem_Malloc实现——若请求分配的内存在1~256字节之间就用内存池管理系统进行分配,调用malloc函数分配内存,但是每次只会分配一块大小为256K的大块内存,不会调用free函数释放内存,将该内存块留在内存池中以便下次使用。
  • 第0层:大内存-----若请求分配的内存大于256K,malloc函数分配内存,free函数释放内存。
  • 第-1,-2层:操作系统进行操作

参考:https://blog.csdn.net/ChaoFeiLi/article/details/100518277

五、内存优化

手动垃圾回收

对python的垃圾回收进行调优的一个最简单的手段便是关闭自动回收,根据情况手动触发。例如在用python开发游戏时,可以在一局游戏的开始关闭GC,然后在该局游戏结束后手动调用一次GC清理内存。这样能完全避免在游戏过程中因此GC造成卡顿。但是缺点是在游戏过程中可能因为内存溢出导致游戏崩溃。

调高垃圾回收阈值

相比完全手动的垃圾回收,一个更温和的方法是调高垃圾回收的阈值。例如一个游戏可能在某个时刻产生大量的子弹对象(假如是2000个)。而此时Python的垃圾回收的threshold为1000。则一次垃圾回收会被触发,但这2000个子弹对象并不需要被回收。如果此时Python的垃圾回收的threshold0为10000,则不会触发垃圾回收。若干秒后,这些子弹命中目标被删除,内存被引用计数机制自动释放,一次(可能很耗时的)垃圾回收被完全避免了。

调高阈值的方法能在一定程度上避免内存溢出的问题(但不能完全避免),同时可能减少客观的垃圾回收开销。根据具体项目不同,甚至是程序输入的不同,合适的阈值也不同。因此需要反复测试找到一个合适的阈值,这也算调高阈值这种手段的一个缺点。

避免循环引用(手动解循环引用和使用弱引用)

一个可能更好的方法是使用良好的编程习惯尽可能的避免循环引用。两种常见的手段包括:手动解循环引用和使用弱引用。

手动解循环引用:

手动解循环引用值在编写代码时写好解开循环引用的代码,在一个对象使用结束不再需要时调用。例如:

import objgraph

 

class A(object):
    def __init__(self):
        self.child = None

    def destroy(self):
        self.child = None

class B(object):
    def __init__(self):
        self.parent = None

    def destroy(self):
        self.parent = None

def test3():
    a = A()
    b = B()
    a.child = b
    b.parent = a
    a.destroy()
    b.destroy()

test3()
print('Object count of A:', objgraph.count('A'))
print('Object count of B:', objgraph.count('B'))
#Object count of A: 0
#Object count of B: 0

使用弱引用

弱引用是指当引用一个对象时,不增加该对象的引用计数,当需要使用到该对象的时候需要首先检查该对象是否还存在。弱引用的实现方式有多种,Python自带一个弱引用库weakref,这里使用weakref改写我们的代码:

def test4():
    a = A()
    b = B()
    a.child = weakref.ref(b)
    b.parent = weakref.ref(a)

test4()
print('Object count of A:', objgraph.count('A'))
print('Object count of B:', objgraph.count('B'))
#Object count of A: 0
#Object count of B: 0

除了使用Python自带的weakref库以外,通常我们也可以根据自己项目的业务逻辑实现弱引用。例如在游戏开发中,通常很多对象都是有唯一的ID的。在引用一个对象时我们可以保存其ID而不是直接引用该对象。在需要使用该对象的时候首先根据去检查对象是否存在。

六、内存泄露与内存溢出

内存泄露

是指程序在申请内存后,无法释放已申请的内存空间就造成了内存泄露,一次内存泄露似乎不会有大的影响,但内存泄露堆积后的后果就是内存溢出。

有del()函数的对象间的循环引用是导致内存泄露的主凶。不使用一个对象时使用:del object来删除一个对象的引用计数就可以有效防止内存泄露问题。通过Python扩展模块gc来查看不能回收的对象的详细信息。可以通过sysy.getrefcount(obj)来获取对象的引用计数,并根据返回值是否为0来判断是否内存泄露。

内存泄露分类(按发生方式分类):

  • 常发性内存泄漏:发生内存泄露的代码会被多次执行到,每次被执行的都会导致一块内存泄露。
  • 偶发性内存泄露:发生内存泄露的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄露至关重要。
  • 一次性内存泄露:发生内存泄露代码只会被执行一次,由于算法上的缺陷,导致总会有一块且仅有一块内存发生泄露。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄露只会发生一次。
  • 隐式内存泄漏:程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄露,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄露为隐式内存泄漏。

内存泄露解决方法:

  • 内存泄露也许是因为活动已经被使用完毕,但是仍然在其他地方被引用,导致无法对其进行回收。我们只需要对活动进行引用的类独立出来或者将其变为静态类,该类顺着活动的结束而结束,也就没有了当活动结束但仍然还被其他类引用的情况。
  • 资源性对象在不使用的时候,应该调用它的close()函数将其关闭掉。
  • 集合容器中的内存泄露,我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。需要在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。
  • WebView造成的泄露,当我们不使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其长期占用的内存也不能被回收,从而造成内存泄露。我们应该为WebView另外开启一个进程,通过AIDL与主线程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁。

内存溢出

指程序申请内存时,没有足够的内存供申请者使用,或者说,给了一块存储int类型数据的存储看空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出,简单来说就是自己所需要的使用的空间比我们拥有的内存大内存不够使用所造成的的内存溢出。

内存溢出原因:

  • 内存中加载的数据量过于庞大,如一次从数据库中取出过多数据。
  • 集合类中有对对象的引用,使用完后未清空,产生了堆积,使得JVM不能回收。
  • 代码中存在死循环或循环产生过多重复的对象实体。
  • 使用的第三方软件中的BUG。
  • 启用参数内存值设定的过小。

内存溢出解决:

  • 第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xms参数一定不要忘记加)
  • 第二步,检查错误日志,查看“OutOfMemory”错误前是否有其他异常或错误。
  • 第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。
    • 重点排查以下几点:
      1. 检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
      2. 检查代码中是否有死循环或递归调用。
      3. 检查是否有大循环重复产生新对象实体。
      4. 检查List、Map等集合对象是否有使用完后,未清除的问题。List、Map等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。
  • 第四步,使用内存查看工具动态查看内存使用情况

标签:管理,对象,内存,回收,引用,泄露,垃圾
From: https://www.cnblogs.com/simpleness/p/17626101.html

相关文章

  • maven系列:依赖管理和依赖范围
    目录一、依赖管理使用坐标导入jar包使用坐标导入jar包–快捷方式使用坐标导入jar包–自动导入二、依赖范围一、依赖管理使用坐标导入jar包1.在pom.xml中编写<dependencies>标签2.在<dependencies>标签中使用<dependency>引入坐标3.定义坐标的groupId,artifact......
  • 红帽认证RedHat-RHCSA 权限管理特殊权限网络配置磁盘管理逻辑卷管理软件管理笔记汇总
    文件/目录的权限和归属 访问权限读取:允许查看文件内容、显示目录列表写入:允许修改文件内容,允许在目录中新建、移动、删除文件或子目录可执行:允许运行程序、切换目录归属(所有权)属主:拥有改文件或目录的用户账号属组:拥有该文件或目录的组账号,组中用户查看文件/目录的权限和归属......
  • 基于ssm框架的小区物业管理系统的设计与实现
    随着社会的发展,社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。小区物业管理系统,主要的模块包括查看首页、个人中心、业主管理、新闻公告管理、楼房信息管理、业主投诉管理、业主报修管理、投诉处理管理、维修回复管理、缴费信息管理......
  • 某公司笔试题 - 求int型正整数在内存中存储时1的个数(附python代码)
    #输入一个int型的正整数,计算出该int型数据在内存中存储时1的个数。#数据范围:保证在32位整型数字范围内num=int(input("请输入一个正整数:"))#将输入的正整数转化成二进制num_bin=bin(num)print(num_bin)#将二进制字符串转化成数组nbl=list(num_bin)iflen(nbl)>0and......
  • 进程管理 & (系统调用 内核同步)
    进程管理在现代操作系统中,进程提供两种虚拟机制,虚拟处理器和虚拟内存PCB描述一个正在执行的程序:打开的文件,挂起的信号,内核内部数据,处理器状态,一个或多个具有内存映射的内存地址空间及一个或多个执行线程。在2.6以前的版本中,PCB直接放在内核栈的尾端,或者放一个pcb_info间接索引......
  • Golang之旅——内存管理
    转载放在最前一文带你了解,虚拟内存、内存分页、分段、段页式内存管理[Golang三关-典藏版]一站式Golang内存洗髓经|Go技术论坛刘丹冰Aceld感谢以上文章作者,收获满满......
  • 如何看待稚晖君的时间管理水平?
    前言 稚晖君究竟是如何安排业余时间去做这么多高水平的项目?而且每个项目的用时也很少,普通人能够从中借鉴一些经验吗?本文转载自计算机视觉life原文链接:https://www.zhihu.com/question/491456524/answer/2183081310仅用于学术分享,若侵权请联系删除欢迎关注公众号CV技术指南,专......
  • JVM——内存分配与垃圾回收
    内存分配与垃圾回收1、jvm简介Java虚拟机在执行Java程序的过程中会把它管理的内存划分为若干个不同的数据区域。它们各有用途,有些随着虚拟机进程的启动一直存在(堆、方法区),有些则随着用户线程的启动和结束而建立和销毁(程序计数器、虚拟机栈、本地方法栈)。JVM的设计者们之所......
  • Linux 共享内存mmap,进程通信
    @TOC前言进程间通信是操作系统中重要的概念之一,使得不同的进程可以相互交换数据和进行协作。其中,共享内存是一种高效的进程间通信机制,而内存映射(mmap)是实现共享内存的一种常见方法。一、存储映射I/O存储映射I/O是一个磁盘文件与存储空间中的一个缓冲区相映射。于是,当从缓冲......
  • openGauss数据库源码解析系列文章——安全管理源码解析(三)
    Gauss松鼠会[openGauss](javascript:void(0);)2023-07-2917:58发表于四川在上篇openGauss数据库源码解析系列文章——安全管理源码解析(一)我们围绕安全管理整体架构和代码概览、安全认证原理介绍和代码解析进行了简单介绍。本篇将继续角色管理、对象权限管理的学习,全文阅读需要3......