内存漏洞
我们在上期的《预装入对象》一文中提到了"内存漏洞(memory leak)",这是程序员和用户都很关心的问题。一个对象被装入并且分配了内存,而在对象被关闭时却没有释放分配的内存,这样,内存漏洞就产生了。 在开发工具中也会有内存漏洞,这是我们无法控制的,但是我们必须注意我们自己的代码所造成的内存漏洞。
最有可能造成内存漏洞的是,使用create语句创建一个对象后没有用对应的destroy语句消除。无论何时,用create函数创建了一个对象,就必须负责在该对象执行完成之后释放分配的内存。
例:创建一个transaction对象:
transaction my_transaction
my_transaction=create transaction
当用完这个对象后,应该清除该对象:
destroy-my-transaction
(注意:sqlca由应用自动创建,同时也自动关闭。)
内存漏洞的出现经常是与开发者使用了非可视化用户对象有关。因为这种对象只能用create语句创建它的一个实例,因此如不对其使用destroy语句消除,则必然导致错误。另外用openuserobject或openuser-objectwithparm函数创建的动态用户对象,同样要求开发者在结束使用它们时调用相应的closeuserobject函数。
我们知道,powerbuilder能够自动清除放在一个窗口中的常规对象,但它为什么不能在应用结束时自动清除用户自己创建的对象呢?因为powerbuilder仅能清除控件列表中的对象,而且只有一个对象(如窗口)及其表面的那些对象才会列在控件列表当中(包括不可见的对象);而动态的用户对象和非可视化对象,与powerbuilder全局对象的实例(transactions、error、message等等)一样,是在对象的控制列表已经创建后加到对象上的。关闭父对象时,系统并不知道要清除这些动态加入的对象。如果开发者没有手工消除它们,它们将一直保持打开状态,并常驻在内存中,直到使用工具来清除,或者关闭windows系统。使用像windows3.1 resource kit中提供的内存资源监控器那样的工具,能使您在测试过程中检查资源以确定资源按照预想的那样被释放。
内存管理
在windows3.x平台上,开发人员编译时会遇到这样的问题:机器里有32m内存,而且只有两个应用在运行,可是powerbuilder却总是提示内存不足(out of memory)。而用户在使用某应用软件时,也会同样出现内存不足的问题,于是用户只得关闭其它的应用,直到发现关闭了某一程序释放了足够的内存空间可供powerbuilder运行为止。于是用户开始抱怨开发者,而开? 开始抱怨powerbuilder。
其实在多数情况下,他们都不应该受责备,问题的根源出在windows本身。我们知道,从严格意义上讲,windows并不是一个操作系统,它只是一个dos应用程序,它仍然要求固定的程序空间。这些空间分布在1m以下的上位内存中。如果您使用带参数/c的dos命令mem,您将会看到类似以下的显示(见右下表)。
尽管在dos内存限制640k和1m上位内存块之间有360k(约384,000字节)的可用内存,但在显示中,"上位"内存和"保留"内存之间却有近500k可用。"保留"内存一般用来装载网络软件和其它的驱动,在"win386"一行也显示还有42k可用,这是被那些需要固定地址空间的应用使用的。当windows创建一项新任务时,windows的装入模块为该任务创建一个任务数据库(tdb)。这个任务数据库必须被装入到1mb以下内存,而且最小长度为200字节。原因是任务数据库项的第二部分是一个程序段前缀(psp),这是windows1.0、2.0和3.0实模式创造的,使用它的唯一原因是更加容易地调度应用内置的ms-dos扩展器。在保护模式的windows中并不是必需的,不过在windows3.1保护模式中仍保留了这一结构。
无论您的机器有多少内存,您的程序必须去争取这段可用空间,没有什么方法可以扩充它。这一限制,以及下面将提到的64k gdi和user堆,是windows3.x中最受限制的代码。
所有的windows程序或多或少需要一些这种1m以下的内存才能正常工作。一般的程序除分配了一些固定的内存外,有些程序还要另外申请一些内存空间,而有些有错误的程序会很快地消耗掉这段有限的内存空间,这就会出现前面我们所提到的内存不足问题。不过powerbuilder并不让我们自己去分配这些稀有的资源,这也使我们无法解决这种原因引起的内存不足问题。
c程序员会很熟悉用gmem_fixed或是用global-dosalloc函数分配内存。这两个函数都将试图分配低于1m的内存。heapwalk.exe(包含在windows sdk或c++编译器中)这样的应用程序能查看1m以下分配的内存块,从而更准确地确定哪些应用吃掉了宝贵的内存资源。可以用ms-dos中的loadhigh功能,将一些驻留程序装入高端而释放常规内存,但这样做也减少了可用的上位内存,最好办法只能是折衷。
其它windows资源
还有两种windows中常常引起问题的资源,它们是user和gdi资源。这些资源就像上面描述的任务数据库区一样,也是被windows而不是被机器内存限制的,它们都有不能超过64k的限制。这意味着有时并不是机器内存总数引起了内存不足。
gdi资源就是应用中用到的资源句柄和设备上下文。每个位图、图标、光标、数据窗口、用户对象和窗口都需要这种资源。大的自定义工具条对gdi资源的需求最大,但它一般不会引起麻烦。窗口、数据窗口和按钮将最终用光gdi资源。gdi资源几乎无一例外地最先减少到20%以下。
user资源也是每个对象都需要的资源。如果一个用户对象由一组对象组成,其中每个对象都需要user资源(句柄、任务管理等等),这种情况下调用动态打开或关闭的对象显然是有益的。因为数据窗口是单一对象,所以使用数据窗口作为对象集合也是给这些资源减少负担的一个好方法。
通过上述介绍,我们可以看到windows3.x中可用的内存远比您最开始想象的要少。windows95也许将会缓解这一问题,但仍需观察。
动态监控资源例程
最后,我们介绍一个能帮您在运行时跟踪您的系统资源的例程。我们曾提到过,使用powerbuilder,我们不能控制系统对资源的使用,但是我们可以用一些简单的sdk函数调用来监控资源的使用。我们将使用一个非可视化对象作为sdk的界面对象。当然您也可以将sdk函数声明为全局外部函数并通过全局函数来访问它们。
步骤一:
创建api访问对象nvo_api_access;
创建一个用于声明的应用事件,并从construc-tor事件中触发该事件;
创建一个用于初始化的用户事件nvo_ue_setup,并从constructor中触发。
步骤二:
声明以下的局部外部函数:
function uint getfreesystemresources(uint sysresource) library user.dll
步骤三:
声明以下变量实例:
private:
/*最小的资源限制*/
long il_usermem_limit
long il_gdimem_limit
long il_memory_limit
long il_standard_threshhold
/*资源访问常量*/
integer sdkuser=0,sdkgdi=1,sdkmemory=2
步骤四:
创建以下的用户对象函数:
//**************************
// 函数: nvof_check_resources
// 功能: to check system resources
// 参数:(无)
// 返回值:integer
// 1-成功,<0-失败(绝对数值为资源访问常量)。
//*************************
if getfreesystemresources(sdkuser) < il_usermem_limit then
return(sdkuser*-1)
end if
if getfreesystemresources(sdkgdi) < il_gdimem_limit then
return(sdkgdi*-1)
end if
if getfreesystemresources(sdkmemory) < il_memory_limit then
return(sdkmemory*-1)
end if
return 1
步骤五:
我们需要创建一个函数用于建立我们的最小资源限制。创建函数nvof_set_resource_limit。输入参数是整型变量ai_resourcetype,它指代可用资源常量的种类(gdi,user或 memory)和ai_threshhold,指代这种资源类型的临界限制值
。
//**************
// 函数: nvof_set_resource_limit
// 作用: sets resource threshhold limits
// 参数: integer ai_resourcetype
// (指代可用资源常量种类)
// integer ai_threshhold
// (这种资源类型的临界限制值)
// 返回值:integer
// 1-成功,-1-失败.
//
//*****************
/*设置资源极限*/
choose case ai_resourcetype
case sdkgdi
il_gdimem_limit=ai_threshhold
case sdkuser
il_usermem_limit=ai_threshhold
case sdkmemory
il_memory_limit=ai_threshhold
end choose
return 1
步骤六:
在声明事件中,将il_default_threshold设为稍大一些的数值。例如,30%可能是您能接受的最低的不用警告用户的资源量,那么我们在声明事件中写下如下代码:
il_default_threshold=30
在初始化事件中,建立缺省函数以设定临界限制,函数可以在运行时重置这些限制。开发过程中将限制设得高一点,这样确保在最后产品运行时资源不会出问题。
nvof_set_resource_limit(sdkuser,il_default_threshold)
nvof_set_resource_limit(sdkgdi,il_default_threshold)
nvof_set_resource_limit(sdkmemory,il_default_threshold)
小结
建立上述对象的一个实例,在打开一个窗口、动态打开一个用户对象或创建一个对象之前,通过调用对象的nvof_check_resources()函数,您可以确定是否有足够的资源用来继续正常打开。这些函数也可以在应用的任何地方调用以重置限制或检查资源。
Java Asp PHP .Net XML C/C++ CGI VB Jsp J2ee J2se J2me EJB Servlet Tomcat Resin Struts Weblogic Eclipse ANT GUI JMS Web servise IDEA Webphere Hibernate Spring Jboss Applet Swing Socket Javamail Perl Ajax P2P 安全 模式 框架 测试 开源 游戏
Windows XP Windows 2000 Windows 2003 Windows Me Windows 9.x Linux UNIX 注册表 操作系统 服务器 应用服务器