内存泄露

2024-11-15

内存泄露(共3篇)

内存泄露 篇1

1 引言

随着人们对网络程序的安全性要求越来越高,Java以其高安全性的特点迅速成为现代最流行的高级编程语言之一。尤其是它特有的内存管理机制———垃圾收集器 (Gargbage Collector, GC) ,减轻了程序员的负担,减少了许多内存泄露的可能性,提高了程序的安全性。然而,这并不是说在Java中不存在内存泄露的问题,只是Java的内存泄露比较隐蔽,为了提高程序的安全性和稳定性,Java中的内存泄露是值得我们深刻分析一下的。

2 Java内存泄露

内存泄露,通常是指分配出去后却无法回收的内存空间。

2.1 传统语言中的内存泄露

在传统语言(如C/C++等)中内存泄露的范围和发生的可能性是十分大的,程序员需要自行管理内存,如果程序中为变量或对象申请了内存空间,则在不需要时必须调用相应的函数进行显式释放它们占用的内存空间,即使超出变量或对象的作用域,否则这块内存将永远得不到回收直至系统重启。因此可见,传统语言中一旦发生内存泄露,其危害性是不言而喻的。

2.2 Java中的内存泄露

针对传统语言的不足,Java中一个很大的改进就是引入了垃圾回收器(GC)的机制,它使程序员从传统语言复杂的内存管理中解放出来,将更多的精力关注于业务逻辑的开发,程序员只需要用关键字new或者用Java的反射机机制为对象开辟一块内存空间,在对象不再使用时,而不需要进行显式的释放,这块空间会被GC自动回收,这种收支两条线的内存管理机制有效地解决了传统语言中的内存泄露问题,极大地提高了编程的效率。尽管如此,GC的引入并不能完全避免Java中的内存泄露。Java中的内存泄露和传统语言中的内存泄露是十分不同的,它是指对象不再被需要时,但却仍被程序无意识地、错误地保持或引用而导致GC无法回收对象所占用的内存空间。因为在GC看来,它们还是“有用”的,即Java中的内存泄露是主观的内存泄露,大多是由于程序员的一时大意而造成的。

可以用图论来描述Java中的内存泄露。把对象看成是有向图的顶点,引用关系看成是有向图的有向边,有向边从引用对象指向被引用对象,线程对象作为有图的起始顶点,如图1所示。

注:执行o2=mull欲释放对象2, 但还被数组对象引用着, 于是被泄了

通过图1可知,Java中的内存泄露的对象具有以下两个特点:首先,这些对象是可达的,即在有向图中存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。

3 表现形式

由于有了GC的帮助,那些“不可达”的对象将不会再被泄露,Java中内存泄露的机率降到了很低。因此,Java中内存泄露往往并不像传统语言中那样表现得很明显,使程序很快出现致命地错误,但往往会在系统运行一段时间后就会暴露出来。

3.1 瞬间泄露

瞬间泄露是指由于程序在短时间内保持了大量的无用对象的引用而导致堆内存不存或耗尽。它对应用程序来说是致命的,在软件开发过程中一般都能被检测出来,因为不解决它,程序是无法正常运行的。其很明显的表现形式就是操作过程中程序瞬间抛出java.lang.OutOfMemoryError,即内存溢出。虽然可以通过扩大堆内存空间的方式解决瞬间泄露的出现,但内存的增长毕竟是有限的,而且这种解决方式很有可能把瞬间泄露转成下面将说的另一种更为隐蔽的泄露形式———缓慢泄露。

3.2 缓慢泄露

缓慢泄露是指程序每次只泄露少量对象,短时间内不足以影响程序的正常运行,但运行时间一长,程序必定会因为内存不足出现java.lang.OutOfMemoryError错误。它具有隐蔽性、泄露周期长的特点,所以在开发过程中最容易被忽视,这部分内存泄露也是Java内存泄露中的最主要形式。下面是一段缓慢泄露的程序。

在上面的例子中,EmulateStack类模拟数据结构中的栈,使用intoStack和outStack方法进行进栈和出栈,pointer指向栈顶位置,在main方法中,初始化了一个大小为5的栈,然后把一个Object对象入栈,接着又把它出栈,这时Object对象占用的空间就被回收,事实上并非如此,outStack方法只是减少了栈顶指针pointer的值,栈中仍然保持着对Object对象的引用,该程序每执行一次,都会泄露一个Object对象。实际上,我们只要修改outStack方法,即可解决内存泄露的问题。修改如下:

4 原因

4.1 客观原因

主要是由于GC的机制所决定的,GC和程序员对垃圾的认知角度是不一样的。在GC看来,凡是不可达的对象都是垃圾,凡是有句柄指向的对象都是正在使用的对象,不应该被回收;而在程序员看来,程序不再需要使用的对象都是垃圾,而实际上,这些“所谓的垃圾”还是被某些正在使用的对象引用着的,程序员认为它应该被回收,而GC却不会回收它们。另外,GC参数的设置不当,也会增大内存泄露的可能性。

4.2 主观原因

主要是由于程序员的编程水平或疏忽大意,而错误地、无意识地保持着某些无用对象的引用而造成的,这在Java内存泄露中十分常见。

在上面的例子中,程序循环申请Object对象,然后将对象加入一个List容器中,然后试图通过o=null将对象所占用的空间释放掉,其实这是不可行的。因为List容器还持有对Object对象的引用,所以GC不会回收这些Object对象,只有用list=null或list.remove (o) 才能释放这些对象。

5 解决方法

5.1 提早预防内存泄露(1) GC调优

不同的JVM采用了不同的垃圾回收机制和启动参数,有的GC是定时启动,有的是当CPU资源空闲时开始收集垃圾,有的是当堆内存不足时才开始收集。因此,优化GC配置对预防内存泄露十分重要。GC的算法和参数对应用程序的影响是十分大的,不适当的垃圾回收机制和参数可能为程序的内存泄露埋下了隐患。

下面将以最流行的JVM———SUN公司的HotSpot虚拟机为例来说明一下GC如何调优。

HotSpot是用“分代”方式来管理堆空间的,它将整个堆空间分成了三块:永久代(Permanent Generation)、年老代(Old Generation)、年轻代(Young Generation)。年老代保存反射创建的对象,年轻代保存刚刚实例化的对象,当年轻代被填满时,GC会将一部分仍存活的年代代对象移入年老代。针对Hotspot的GC,以下几条优化的原则:

1)最好将-Xms和-Xmx设为相同值,让-Xmn的值等于-Xmx的1/3。

2)一个GUI程序最好是每10到20秒间运行一次GC,每次在半秒内完成。

3)增加Heap空间的大小虽然会降低GC的频率,但也增加了每次GC的时间,并且GC运行时所有的用户线程被暂停,也就是GC期间,Java应用程序不做任何工作。

4)尽可能增大Heap空间,除非应用程序遇到了较长的响应时间。

(2)良好的编程习惯

高效优质的代码可以在很大程度上减少内存泄露的可能性。为了避免内存泄露,最主要的原则就是尽早释放对“无用”对象的引用,即在对象不再需要时,用“对象=null”的方式显式释放对象,以便GC能尽早回收它所占用的内存空间。许多程序员在使用临时变量时,总是让它在退出作用域后自动释放所引用的对象,这对于一些逻辑结构简单的程序可能影响并不大,但对引用关系较为复杂的大型应用,就有可能对临时变量还持用一些错误引用而导致临时对象不能被释放。下面给出几条提高编码效率的建议。

1)尽量少用临时对象

临时对象的存活周期非常短,很快就会变成垃圾,它会使GC频繁启动,从而降低应用程序的性能。

2)尽量不要显式调用System.gc ()

因为此方法只是建议JVM进行垃圾回收,至于什么时候回收还是不确定的,JVM可能会在不该进行回收时而启动GC,导致应用程序临时中断。

3)尽量少用finalize方法,它会使GC的收集时间增长。

4)对象在使用时再实例化,无用时尽早释放对象的引用,即对象句柄=null。

5)尽量避免在类的构造函数中创建大量对象,防止在调用其自类的构造方法时造成不必要的内存资源占用。

6)尽量不要显式申请数组空间,这样会造成堆空间浪费。

7)能用基本类型的就不要用封装类型,如能用int型的,就不要用Integer类型。

8)避免过深的类层次结构和过深的方法调用,因为这两者都是十分耗内存的。

9)对于字符串的操作,尽量用StringBuffer类的appand方法,不要使用String及+,因为对String的每次操作都会产生新的对象。

10)尽量少用static变量,因为它属于全局变量,直到应用程序退出才会被GC回收。

5.2 内存泄露的检测

(1)代码走查

它是安排有经验的开发人员或对整个程序代码很了解的人员对系统进行仔细排查,找到内存泄露的地方。它对于引用关系不是太复杂的小型系统往往十分有效。

(2)利用专业工具

市场上检测Java内存泄露的工具十分多,如JDK6.0的命令行工具JPS, Borland公司的OptimizeIt, Ej-technologies公司的Jprofiler等,它们的工作原理大同小异,都是通过监测Java程序运行时所有对象的创建、释放等动作,将内存管理的所有信息进行统计、分析、可视化,开发人员将根据这些信息判断程序是否有内存泄露的问题。下面简单介绍一下Jprofiler查找内存泄露的基本思路。

Jprofiler 5.1.3是一个全功能的Java剖析工具,专用于分析J2SE和J2EE应用程序,它直觉式的GUI让你可以找到效率瓶颈,抓出内存泄露,并解决执行绪的问题。Jprofiler的内存视图就是用来观察系统运行时堆内存的大小,实际使用的大小和各个类的实例分配个数。如图2所示,各列自左到右分别为类名称、当前实例个数、自上次标记点增长或减少的实例个数、占用内存的大小,最下一行是当前JVM的汇总数据。

在现实生产中,可以分别在系统运行2小时为间隔点,点击“快照”按钮,记录消退时的内存状态,抓取当时的内存快照,找出对象个数增长比较靠前的类,记录这些类的当前对象个数,记录数据后,点击上面的“标记”按钮,将该点的状态作为下一次记录数据的比较点,一个正常的系统其运行时的内存占用量一般是比较稳定的,不会随着时间的增长而增长,同样,一个类的对象也是有一个上限值的,不会无限制地增长,可以通过得到的内存快照,对这些快照进行综合全面的分析,如果有某类对象的内存占用空间一直都在增长,那么就可以初略认定该类对象可能存在内存泄露,接下来,再只对这些可疑对象进行仔细监控分析,必定会找到内存泄露的对象和地方。

6 结语

综上所述,Java的内存泄露主要是由于一些无用对象被错误地保持着,导致它们的空间不能被GC回收造成的。因此,它经常并不容易被发现,本文旨在帮助大家更容易地找出内存泄露,解决性能瓶颈,提高程序的稳定性。

参考文献

[1]陈小玉.Java内存泄漏泄露问题的改进与研究[J].微型电脑应用, 2005, 21 (7) .

[2]关锋, 卢铁, 关威.关于Java的内存泄漏[J].信息技术, 2003, 27 (6) .

[3]Jonathan Knudsen, Patrick Niemeyer.Learning Java, 3rd Edition[M].O’Reilly, 2005.

[4]于海雯, 刘萍等.Java的内存管理与垃圾收集机制分[J].电脑知识与技术, 2006, 20.

[5]朱颖芳.关于Java语言内存泄漏问题的探讨[J].电脑知识与技术, 2006, (32) .

内存泄露 篇2

是不是说没有一种内存检查工具能够在linux使用呢,也不是,像valgrind工具还是相当不错的。他的下载地址是valgrind.org/downloads/current.html#current 下载一个valgrind 3.2.3 (tar.bz2) 工具,按照里面的README提示,安装后就可以使用这个工具来检测内存泄露和内存越界等,

这是一个没有界面的内存检测工具,安装后,输入valgrind ls -l 验证一下该工具是否工作正常(这是README里面的方法,实际上是验证一下对ls -l命令的内存检测),如果你看到一堆的信息说明你的工具可以使用了。

在编译你的程序时,请设置-g参数,编译出后使用如下的命令来判断你的程序存在内存泄露:

内存泄露 篇3

内存泄漏[1]是指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄露会使应用程序申请动态内存失败,导致服务中止;严重时会导致整个系统因资源耗竭而崩溃。一般,我们常说的内存泄露是指堆内存的泄露。目前,虚拟技术作为云计算的关键技术得到了迅速发展,而虚拟机的安全问题越来越受到学术界和工业界的重视,虚拟机的内存管理和普通应用程序的内存管理一样,存在着内存泄露的风险。

一般地,内存泄漏的检测方法分为两大类[2],一类是动态检测方法,另一类是静态检测方法。动态检测方法主要原理是在程序中进行动态内存分配时,在堆中作以标记。当程序退出并释放所有已分配的内存时,检查堆上残留的对象,这些残留的对象就是程序中泄漏的内存。这种分析方法能够直接发现实际发生的程序缺陷。但是,动态特性要求实际执行程序引入较大性能和时间开销,另外由于程序执行的路径覆盖存在死角,检测结果完备性不足,漏报率较高。静态检测方法通过分析程序源代码,模拟所有可能执行路径,判定程序可能执行路径中存在的安全缺陷。这种方法无需实际执行程序,克服了动态分析性能开销较大的问题,但是由于静态分析无法精确判断程序的输入、环境变量等信息,模拟执行路径中可能存在不可行路径。因此,静态分析方法较高的误报率是至今未解的一个重要难题。本文通过研究Xen虚拟机的内存管理机制,提出了一种动态检测虚拟机中内存泄露的方法,该方法通过在虚拟机运行时,修改开源软件Valgrind的源码,在其中插入监测代码,动态截获虚拟机中申请和释放内存的函数,并记录下来,用于辅助甄别内存泄露。与现有技术相比,本文所述的方法能够发现潜在的内存泄露,且不需要修改被探测程序的源代码,也不需要重新编译,为被测试代码提供了透明性。

1 背景和相关工作

1.1 Xen简介

Xen[3]是剑桥大学开发的一种开源虚拟机监控器(VMM或Hypervisor)。Xen直接运行在物理硬件之上,并向上提供可以运行操作系统的虚拟化环境,称为域。VMM具有最高特权级,控制运行在其上的域。Xen上有一个特权域称为Dom0,其他域称为DomU 。Dom0控制硬件设备并为用户提供管理DomU的接口。Dom0是Xen启动后运行的第一个虚拟域,在系统中具有重要的作用,一方面它具有管理控制功能,为使用者提供用户界面;另一方面它是设备驱动程序域,运行着所有硬件设备的驱动。Xen的体系结构如图1所示。

Xen支持半虚拟化和全虚拟化的客户机。在半虚拟化方式下,客户机需要修改操作系统源代码,通过Xen提供的Hypercall接口来完成特权操作;全虚拟化不要求修改客户机代码,但是需要CPU的硬件虚拟化支持[4],主要用于Windows等闭源操作系统。

1.2 相关工作

目前,国内外内存泄露检测的技术和方法很多,检测工具也有很多已经产业化。一般地,用于检测虚拟机内存泄露的方法[5]主要有:

1) 静态方法 这类方法采用基于源代码的静态检测方法,主要检查程序语义它是利用对源代码的静态分析来查找程序缺陷的一种检测方法。由于检测目标程序时不需要运行测试用例,减轻了构建测试平台的开销(如计算能力要求,存储空间要求等)。静态分析能够遍历程序中的每一个分支,它所提供的信息一般都比较全面而正确,而随之而来的也是分析实现的高代价和相对较高的误报率。

2) 动态方法 动态检测方法包括运行时动态检测法和内存回收的方法。动态检测法即在程序运行时记录程序动态分配的内存资源和释放信息,然后分析是否存在内存泄露。例如IBM Rational Purify工具属于这一类。动态检测法受限于测试程序,因为测试程序所覆盖的代码非常有限,无法激发出所有潜在的问题。动态分析是在实际运行过程中基于具体数据的基础上,因此具有较高的准确性。另一种动态检测方法是内存回收的方法。一些编程语言如Java采用了Smart Pointer和Garbage Collection[6]的机制来进行内存的管理。内存回收算法的功能是自动进行内存搜索,从而免去程序员的手工操作,然而这种机制不但不能保证消除内存泄露,而且还会带来性能的损失,因而它在C/C++领域的应用不是十分普遍。垃圾收集存在一些缺陷:垃圾收集暂停、因为垃圾收集而产生的CPU时间损失等。因此,垃圾收集需要付出程序执行效率上的代价,包括对性能的影响、暂停、配置复杂性和不确定的结束等[7]。

本文通过研究Xen虚拟机的内存管理机制和虚拟机中的内存虚拟化技术,提出了一种动态检测虚拟机中内存泄露的方法。该方法基于虚拟机Xen的虚拟机监控器VMM,对运行在虚拟机中的应用程序申请和释放内存资源进行动态捕获和记录,属于动态的检测方法。通过修改开源软件Valgrind[8]的源码,在其中插入监测代码[9],并且修改Valgrind与Xen虚拟机交互的Hypercall代码,进而动态截获虚拟机中申请和释放内存的函数,并在此基础上分析应用程序所申请的内存资源,推测出存在内存泄露的原因并进行监控。同时,利用Valgrind工具的判定内存泄露的规则,找出系统中应用程序的内存泄露的原因并最终给出分析报告。

2 方法的设计与实现

2.1 原 理

研究发现,内存申请和释放的接口具有统一、简单的特色。例如Windows提供的动态内存管理接口GlobalAlloc和GlobalFree、Posix规定的malloc和free接口、Linux内核的kmalloc、vmalloc和vfree等接口[10]。因此,动态检测方法通过研究内存管理机制,截获内存申请和释放的函数,对源代码进行修改。例如可以通过重载malloc/free等内存管理函数、memcpy/strcpy/memset等内存读取函数和指针运算等,以达到控制内存操作功能。在C语言中,对于内存的分配与回收是通过malloc以及free实现的(C++中类似,用new及delete)。因此,可以重载这两个方法,通过钩子程序及宏定义的方式替换,即将标准C语言中的malloc以及free函数利用钩子程序将其重定向到自己实现的另外的两个函数my_malloc及my_free中,在这两个自定义的函数中它们所起的作用同标准C语言库中的malloc以及free所起的作用基本一致,即进行地址的分配与回收。但是,其中增加了两个功能,一个功能是记录下调用这个函数的C源文件以及在源文件中所处的位置。另外一个功能是在自定义的一张Hash表中增加或删除此次调用的所有信息。综上,通过管理所有内存块,无论是堆、栈还是全局变量,只要有指针引用它,它就被记录到一个全局表中[11]。记录的信息包括内存块的起始地址和大小等。对于在堆里分配的动态内存,可以通过重载内存管理函数来实现。对于全局变量等静态内存,可以从符号表中得到这些信息。

虚拟机Xen的虚拟机管理器Hypervisor[12]的内存泄露检测方法与已有的检测方法的显著不同之处在于:因为Xen采用了影子页表[13]、内存虚拟化管理[14]等技术,使得Guest OS对内存的申请和释放得通过陷入Hypervisor的超级系统调用Hypercall。因此,为了检测Xen Hypervisor的内存泄露问题,本文提出修改Valgrind,使其支持对Hypercall的检测,从而完成对于Xen的内存泄露检测。因为Valgrind工具的检测原理是通过修改可执行文件来进行内存泄露检测,所以不需要重新编译程序。但它并不是在执行前对可执行文件和所有相关的共享库进行一次性修改,而是和应用程序在同一个进程中运行,动态地修改即将执行的下一段代码。Valgrind是插件式设计的,它的Core部分负责对应用程序的整体控制,并把即将修改的代码,转换成一种中间格式,这种格式类似于RISC指令,然后把中间代码传给插件。插件根据要求对中间代码修改,然后把修改后的结果交给Core。Core接下来把修改后的中间代码转换成原始的x86指令,并执行它。

基于以上原理,本文提出一种基于虚拟机的动态检测内存泄露的方法。该方法的具体实现为首先分析Xen内核源码中与内存分配和释放相关的代码,重点分析对象为Xen源码中内存管理模块的Hypercall函数,然后修改开源工具Valgrind,在Valgrind的MemCheck功能模块里面,利用钩子函数和宏定义的方式重载/替换插入监测代码,动态截获Xen中申请和释放内存的函数,将这些函数替换为我们定义的钩子函数,当函数启动时,函数里记录调用函数的文件及行号信息,并且定义一张全局Hash表来记录所有调用信息和内存分配情况,包括堆、栈和全局变量,同时根据此信息维护一个动态资源使用列表。当函数退出时,扫描最后的游离对象。当虚拟机运行时,重编译后的Valgrind的memcheck工具用于实现内存泄露的检测。

2.2 整体架构

本文提出的虚拟机内存泄露检测方法是在虚拟机监控器(VMM,在Xen中称为Hypervisor)[15]中实现的,并对客户操作系统和应用程序透明。图2为此方法的整体架构。

DomU里的应用程序向Dom0申请内存,当虚拟机运行时,启动修改后的Valgrind,通过ioctl接口陷入Xen Hypervisor的Hypercall调用,利用Valgrind里的检测函数,截获这些系统调用,替换为钩子函数,获得调用函数的内存分配和释放信息,并分析其内存泄露情况。

2.3 关键技术

(1) 内存虚拟化技术

虚拟化为整个机器提供虚拟的设备,内存虚拟化的一般方法包括虚拟化页表和影子页表技术。VMM对物理内存有最终的控制权,它控制将客户物理地址空间映射到主机物理地址空间从而顺利的实现内存虚拟化。

虚拟化页表有几种实现形式,第一种是被称为保护访问的页表。其主要特点为:① 使用了页保护来探测页表的变化。② 读页表的时候会产生VM退出,Guest OS保留原始的PTE值。

第二种实现内存虚拟化技术的方式是影子页表。首先Guest OS要能够自由的改变页表。Xen实现内存虚拟化的方法是影子页表技术[16]。影子页表技术为Guest OS 的每个页表维护一个“影子页表”,并将合成后的映射关系写入到“影子”中,Guest OS 的页表内容则保持不变。最后,VMM 将影子页表交给内存管理模块MMU(Memory Management Unit)[17]进行地址转换。

(2) Hypercall的实现机制[18]

与系统调用类似,Xen中的Hypercall是通过软中断(中断号0x82)来实现的。首先在文件xen/include/public/xen.h中定义了45个超级调用,其中有7个是平台相关调用。而在文件xen/arch/x86/x86_32/entry.S中定义了超级调用表,通过超级调用号索引就可以方便的找到对应的处理函数。超级调用页是Xen为Guest OS准备的一个页,可以做到不同Guest OS有不同的超级调用页内容。它的实现过程为:_HYPERVISOR_xxxx在超级调用页上找到相应的代码;_hypercall2()调用超级调用页内的代码;通过hypercall_page_initialise实现HVM、Dom0、DomU的不同跳转;调用hypercall()来检查有效性、执行相应的服务例程(do_xxhyper名_)并返回。

超级调用只能由内核来调用,而应用程序无法直接调用。应用程序申请超级调用的过程为:首先打开Xen提供的内核驱动:/proc/xen/privcmd;然后通过ioctl系统调用来间接调用hypercall;如下所示:

复杂一点的超级调用申请的过程为(以_HYPERVISOR_domctl超级调用为例):首先通过pyxc_domain_create()获取要创建的domain的相关信息;然后通过xc_domain_create()创建控制结构体变量domctl;接着通过do_domctl()生成超级调用请求;然后传递请求到OS内核:do_xen_hypercall();最后do_privcmd通过ioctl来完成由3环到1环的转变,并完成超级调用。

3 实验验证与分析

如前所述,本文提出的方法主要采用动态分析方法,采用了Valgrind套件中的Memcheck工具,通过修改Valgrind,使得它能支持Xen的privcmd ioctls / hypercalls,从而利用Memcheck工具,动态检测Xen Hypervisor中实际存在的内存泄露问题。

Valgrind是一个GPL的软件工具包。该工具包主要用于Linux(For x86, amd64 and ppc32)程序的内存调试和代码剖析。Valgrind的工具包主要包括Memcheck、Callgrind、Cachegrind等工具,每个工具都能完成一项任务调试、检测或分析。可以检测内存泄露、线程违例和Cache的使用等。

3.1 实验环境及实验过程

在开发基于Xen虚拟机的安全操作系统的过程中,我们运用了第3节中介绍的内存泄露检测方法对Xen虚拟机的脆弱性进行了验证。实验采用的硬件平台为联想 ThinkCentre台式电脑(CPU为英特尔酷睿2四核Q6600@2.40GHz,内存为4GB),系统搭建了以Fedora8(内核为2.6.18.8)为Domain0,以Xen3.3.3为Hypervisor,以Fedora12为DomainU的实验环境。实验过程分为两步:

第一步:工具的改进、配置与安装。首先下载valgrind-3.6.1源码,依据第3节所述的方法编写支持Hypercall的补丁。在Xen内存管理函数里,我们定义了若干Hook函数用于截获其内存申请和释放,具体包括了受监控函数的类别、参数、获取函数调用栈、申请和释放函数的地址、动态更新内存资源等占用信息。并且维护资源使用列表,程序退出后扫描剩余孤儿对象。我们将上述补丁文件名为xen_valgrind_patch.patch。使用命令:patch-p1<../xen_valgrind_patch.patch运行该补丁。然后使用命令aclocal && autoheader && automake-a && autoconf,通过Gnu autoconf和automake工具重新生成Makefile文件。最后使用命令./configure-with-xen进行配置,并使用make工具对修改后的Valgrind源代码进行编译和安装。至此,支持Hypervisor的Valgrind安装完成。

第二步:动态执行和分析。运行修改后的Valgrind工具memcheck,分析目标代码中内存泄露和错误使用。例如为了检测GCC工具的内存泄露,启动Valgrind:Valgrind --tool=memcheck--track-origins=yes--trace-children=yes--leak-check=full --show-reachable=yes --log-file="out.log" gcc-o a.out test.c;其中--leak-check=full 指的是完全检查内存泄漏,--show-reachable=yes是显示内存泄漏的地点,--trace-children=yes是跟入子进程,--log-file=filename将输出的信息写入到filename的文件里。最后分析文件out.log输出的debug信息并定位发现内存泄露的代码位置。

3.2 实验结果

经过对实验文件out.log进行分析,实验表面,在本文所述平台上,利用本文设计的工具,可动态的检测出虚拟机下的内存泄露情况,实验结果归纳如下:

(1) 确定性内存泄露。检测结果代码片段为:

此处表明setup_probe_watch中的236行调用了malloc函数,但是没有对其进行处理,造成了内存泄露,而setup_probe_watch在libblktap.so.3.0.0库中。libblktap.so.3.0.0是Blktap的动态运行库,而Blktap是一个运行在Domain 0用户空间的程序,给用户层提供对虚拟磁盘文件读写操作的接口。blktap目录中定义了一些用户层的虚拟磁盘访问接口。

(2) 使用未初始化的内存。检测结果代码片段为:

此处表明在文件xc.c的1540行处调用的pyxc_sched_id_get函数使用了未初始化的变量,该函数被libxenctrl的xc_sched_id调用;同样在xc.c文件的1152行处调用了pyxc_physinfo使用了未初始化的变量,在xc.c文件的1336行处调用了pyxc_numainfo使用了未初始化的变量,该函数被libxenctrl的xc_availheap调用。xc.c是xen的影子页表实现的重要组成部分,它主要实现了对分页paging的支持;而libxenctrl是一个C库,它提供了一些简单易用的API,使用户程序可以方便地和Hypervisor进行通信。它的工作原理很简单,它封装了dom0中的/proc/xen/privcmd,/dev/xen/evtchn以及/dev/xen/gntdev提供的IOCTL接口。

3.3 性能评价与分析

本文设计了如下实验用来测试本实现对运行在Xen之上的应用程序的性能影响。利用本论文设计的工具,我们截获了GCC中的内存申请和释放的函数,然后检测虚拟机Xen平台下GCC的内存泄露情况,并且对比了非虚拟机平台下GCC的内存泄露情况。最终结果如表1所示。

由表1可知, GCC4.4.5在正常的Xen虚拟机上编译一个应用程序所需时间为25秒,而利用本文的内存泄露检测工具在Xen虚拟机上编译同一个应用程序所需时间为27秒,可见内存泄露探测工具引起的性能损失在10%以内。

4 结 语

内存泄露是影响信息系统研究可用、可靠的基础性问题。由于内存虚拟化技术的应用、虚拟机的内存管理更加复杂,引起内存泄露的因素极大地增加了。

本文通过分析Xen虚拟机内核中影响内存资源分配和释放的资源,设计并实现了一种基于Xen虚拟机的动态内存泄露检测方法。与其他基于虚拟机架构的方案相比,该方法可扩展性强、易用性好,对系统资源的保护比较全面,并且具有较高的效率。存在的不足和改进的方向如下:

(1) 能够检测 new/delete/malloc 引起的内存泄漏问题,但是对于一些内存分配的 Windows API 函数(如 RtlHeapAlloc)并没有拦截检测,尽管这些函数出现概率不高,但是在特定的程序中可能频繁出现。下一步将增加相应的拦截函数,跟踪此类函数产生的堆内存的变化情况,以检查此类内存泄漏问题。

(2) 鉴于多线程程序在实际中的广泛应用,下一步将扩展实现支持多线程的堆内存泄漏检测。

摘要:内存泄露是一种常见的系统安全问题。虚拟技术是云计算的关键技术,虚拟机环境下的内存泄露不容忽视。而基于虚拟机的内存泄露检测技术尚未成熟。分析虚拟机Xen内核源码中与内存分配有关的代码,提出一种动态检测虚拟机中内存泄露的方法。该方法记录应用程序对资源的申请、释放以及使用情况,插入监测代码,最终检测出内存泄露的代码。实验结果表明,该方法能够有效地检测Xen虚拟机中的内存泄露。

上一篇:同步模型下一篇:体验研究