urlopen内存泄漏浅析

1.背景

  urllib,urllib2是客户端http协议的实现,urllib2底层使用httplib,socket库,它主要包含urlopen, build_opener, install_opener等func。python2.7使用urllib2库中的urlopen会出现内存泄漏的现象,可以通过gc模块来视察内存泄漏情况。

# -*- coding: utf-8 -*-
#!usr/bin/python
import urllib2
import socket
import gc

# check memory on memory leaks
def get_unreachable_memory_len():
    #当设置DEBUG_SAVEALL后,所有unreachable对象会append到garbage中,不会被销毁,从而进行视察,测试时使用。
    gc.set_debug(gc.DEBUG_SAVEALL)
    gc.collect()
    unreachableL = []
    for it in gc.garbage:
        unreachableL.append(it)
        #print(str(it))
    print str(unreachableL)

def task():
    try:
        req = urllib2.urlopen(‘http://www.baidu.com/‘, timeout=3)
        text = req.read()
        #req.fp._sock.recv = None
        req.close()
    except urllib2.HTTPError, e:
        print e.code
    except urllib2.URLError, e:
        print e.reason
    else:
        print("urlopen success")

if __name__ == ‘__main__‘:
    get_unreachable_memory_len()
    print("-------------------------")
    task()
    print("-------------------------")
    get_unreachable_memory_len()

运行程序确定urlopen存在内存泄漏:

2.现象分析

  python垃圾回收机制基于对象的引用计数,所以先找到造成循环引用的代码。采用objgraph模块打印出增加的对象。示例代码如下:

# -*- coding: utf-8 -*-
#!usr/bin/python
import urllib2
import socket
import gc
import objgraph

# check memory on memory leaks
def get_unreachable_memory_len():
    #当设置DEBUG_SAVEALL后,所有unreachable对象会append到garbage中,不会被销毁,从而进行视察,测试时使用。
    gc.set_debug(gc.DEBUG_SAVEALL)
    gc.collect()
    unreachableL = []
    for it in gc.garbage:
        unreachableL.append(it)
        #print(str(it))
    print str(unreachableL)

def task():
    try:
        req = urllib2.urlopen(‘http://www.baidu.com/‘, timeout=3)
        text = req.read()
        #req.fp._sock.recv = None
        req.close()
    except urllib2.HTTPError, e:
        print e.code
    except urllib2.URLError, e:
        print e.reason
    else:
        print("urlopen success")

#class HTTPResponse(object):
#    pass

if __name__ == ‘__main__‘:
    gc.set_debug(gc.DEBUG_SAVEALL)
    objgraph.show_growth()
    print("-------------------------")
    for i in range(5):
        task()
    print("-------------------------")
    objgraph.show_growth()

看到引用计数加5的三个字段,以及观察到上一次运行结果首先出现的是httplib.HTTPResponse。

使用objgraph.show_backrefs对httplib.HTTPResponse进行分析:

# -*- coding: utf-8 -*-
#!usr/bin/python
import urllib2
import socket
import gc
import objgraph

# check memory on memory leaks
def get_unreachable_memory_len():
    #当设置DEBUG_SAVEALL后,所有unreachable对象会append到garbage中,不会被销毁,从而进行视察,测试时使用。
    gc.set_debug(gc.DEBUG_SAVEALL)
    gc.collect()
    unreachableL = []
    for it in gc.garbage:
        unreachableL.append(it)
        #print(str(it))
    print str(unreachableL)

def task():
    try:
        req = urllib2.urlopen(‘http://www.baidu.com/‘, timeout=3)
        text = req.read()
        #req.fp._sock.recv = None
        req.close()
    except urllib2.HTTPError, e:
        print e.code
    except urllib2.URLError, e:
        print e.reason
    else:
        print("urlopen success")

#class HTTPResponse(object):
#    pass

if __name__ == ‘__main__‘:
    gc.set_debug(gc.DEBUG_SAVEALL)
    print("-------------------------")
    for i in range(5):
        task()
    print("-------------------------")
    objgraph.show_backrefs(objgraph.by_type(‘HTTPResponse‘)[0], max_depth = 10, filename = ‘obj.dot‘)

将生成的obj.dot转化为obj.png(使用命令dot obj.dot -Tpng -o obj.png)图示如下,记录下造成循环引用的recv引用和read方法。

3.源码追踪

查看urllib2类图可以使用pycharm自动生成UML类图,这里需要分析urllib2.urlopen的调用流程,可以引入pycallgraph模块来分析,示例代码入下:

# -*- coding: utf-8 -*-
#!usr/bin/python
import urllib2
import socket
import gc
from pycallgraph import PyCallGraph
from pycallgraph.output import GraphvizOutput

def task():
    graphviz = GraphvizOutput()
    graphviz.output_file = ‘urlopen.png‘
    with PyCallGraph(output=graphviz):
        try:
            req = urllib2.urlopen(‘http://www.baidu.com/‘, timeout=3)
            #text = req.read()
            #req.fp._sock.recv = None
            #req.close()
        except urllib2.HTTPError, e:
            print e.code
        except urllib2.URLError, e:
            print e.reason
        else:
            print("urlopen success")

if __name__ == ‘__main__‘:
    task()

截取部分生成的调用流程图:

在HTTPHandler类中的do_open方法中有这一行代码:

这个r指的是HTTPResopnse类,它只有read方法而没有recv方法,这个引用在urlopen调用结束后并没有释放。解决内存泄漏问题就需要消除改引用。

4.解决方法:

1)上述示例当中调用task()之后使用gc.collect()进行手动内存回收。

2)http连接close之前手动解决r.recv这个引用。

 req = urllib2.urlopen(‘http://www.baidu.com/‘, timeout=3)
text = req.read()
#对于调用urlopen正常返回的情况手动解除r.recv = r.read这个引用
req.fp._sock.recv = None
req.close()

注:当返回错误状态码urllib2.HTTPError时无法生效,需要修改urllib2.py源码为

3)改用更底层的socket,httplib库。

参考资料:

1)http://python.jobbole.com/88827/

2)https://bugs.python.org/issue1208304

3)https://stackoverflow.com/questions/4214224/how-to-solve-python-memory-leak-when-using-urrlib2#

原文地址:https://www.cnblogs.com/anjike/p/10230302.html

时间: 2024-08-29 15:24:55

urlopen内存泄漏浅析的相关文章

浅析c#内存泄漏

一直以来都对内存泄露和内存溢出理解的不是很深刻.在网上看到了几篇文章,于是整理了一下自己对内存泄露和内存溢出的理解. 一.概念 内存溢出:指程序在运行的过程中,程序对内存的需求超过了超过了计算机分配给程序的内存,从而造成“Out of memory”之类的错误,使程序不能正常运行. 造成内存溢出有几种情况: 1.计算机本身的内存小,当同时运行多个软件时,计算机得内存不够用从而造成内存溢出.对于这种情况,只能增加计算机内存来解决. 2.软件程序的问题,程序在运行时没能及时释放不用的内存,造成使用的

浅析Context及可能带来的内存泄漏问题

什么是 Context 纯英文含义来看,Context 意指上下文.环境.背景等等--那么 Android 中的 Context 的含义和这些英文释义有什么联系呢?不妨看看 Google 给出的定义: Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. I

java的GC与内存泄漏

从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C语言的同学都知道,在C语言中内存的开辟和释放都是由我们自己来管理的,每一个new操作都要对于一个delete操作,否则就会参数内存泄漏和溢出的问题,导致非常槽糕的后果.但在Java开发过程中,则完全不需要担心这个问题.因为jvm提供了自动内存管理的机制.内存管理的工作由jvm帮我们完成.这样我们就不

内存管理 浅析 内存管理/内存优化技巧

内存管理 浅析 下列行为都会增加一个app的内存占用: 1.创建一个OC对象: 2.定义一个变量: 3.调用一个函数或者方法. 如果app占用内存过大,系统可能会强制关闭app,造成闪退现象,影响用户体验.如何让回收那些不再使用的对象呢?本文着重介绍OC中的内存管理. 所谓内存管理,就是对内存进行管理,涉及的操作有: 1.分配内存:比如创建一个对象,会增加内存占用: 2.清除内存:比如销毁一个对象,会减少内存占用. 内存管理的管理范围: 1.任何继承了NSObject的对象: 2.对其他非对象类

检查内存泄漏

1.分配空间 2.记录内存块信息 3.调用构造函数(类型萃取) #include<iostream> #include<string> #include<list> #include<assert.h> using namespace std; struct BlockInfo { void* _ptr; string _file; int _line; BlockInfo(void *ptr, const char*file, int line) :_pt

内存泄漏工具VLD1.0_要点分析

0X01 关闭FPO优化 // Frame pointer omission (FPO) optimization should be turned off for this // entire file. The release VLD libs don't include FPO debug information, so FPO // optimization will interfere with stack walking. #pragma optimize ("y", of

内存泄漏-Node

内存泄漏: 1.缓存 2.队列消费不及时 3.作用域未释放 缓存: 必须要有过期策略 1.缓存限制策略 limitablemap LRU 2.缓存解决方案 进程自身不存储状态,进程外缓存 1)能减少常驻内存的对象的数量,让垃圾回收更高效 2)进程之间可以共享缓存 常用的缓存: Redis Memcached 内存泄漏-Node,布布扣,bubuko.com

只运行一个实例以及内存泄漏检测

unit 使应用程序只运行一个实例; interface uses Windows; const  // - 互斥体唯一的名字  _Mutex_Name = '{19631971-1976-1981-1989-199319941995}'; var  _Mutex_Handle: THandle; implementation initialization // - 载入时调用的代码 // - 创建互斥体对象_Mutex_Handle := CreateMutex(nil, False, LPC

SGI STL内存配置器存在内存泄漏吗?

阅读了SGI的源码后对STL很是膜拜,很高质量的源码,从中学到了很多.温故而知新!下文中所有STL如无特殊说明均指SGI版本实现. STL 内存配置器 STL对内存管理最核心部分我觉得是其将C++对象创建过程分解为构造.析构和内存分配.释放两类操作分离开来!摆脱了对频繁调用new或malloc函数想操作系统申请空间而造成的低效.其中析构操作时对具有non-trival.trival 析构函数的class区别对待也提高了效率.SGI 的两级配置器结构属于锦上添花. STL内存配置器有没有内存泄漏?