32. Lotus Notes中的垃圾回收之LotusScript

Java使得垃圾回收的概念不再只有环保人士熟悉,还成为程序员的必修课。我们先来看看它的定义:垃圾回收是一种自动内存管理的形式。回收器监测程序中的对象,当它们不再被需要时,收回其占用的内存。与之相对的是程序员人为指定何时销毁对象。
实际上,垃圾回收的产生很自然。在函数中声明的各种基本数据类型的变量都是在栈(stack)里创建,退出函数时占用的内存因为栈弹出被自动回收。既不需要程序员注意,计算机也不必特殊处理。但是栈作为实现函数调用的机制,只适合于保存较小的数据。如果是复杂的数据类型(体积较大),比如一个对象,就会被创建在其他专门的内存区域,在Java里就是堆(heap),栈里只保存引用这个对象的指针。那么当函数退出,其中的局部变量失效时,它们引用的对象仍然滞留在内存中。像C++这样的语言就要求程序员手工销毁对象,实践证明这项工作既繁琐又容易出错。人不想做,就想到让计算机代劳,而干繁琐又容易出错的活正是机器的专长——细致和不知疲倦。于是垃圾回收技术就诞生了。
回到Lotus Notes中来,垃圾回收也一直在发挥作用,只是进行得悄无声息,以至于大部分人都没有注意到它。在LotusScript中,我们使用Notes对象,只管创建,它们退出作用域之后占用的内存都是被自动回收的。这实际上很自然,因为LotusScript所源于的VBA使用的COM对象就采用了基于引用计数的垃圾回收。
下面就用几个例子来测试一下读者诸君对LotusScript里垃圾回收特性的了解。
例子1
Function GetConfigDoc(key As String) As NotesDocument
	Dim s As New NotesSession
	'Get the db containing the config documents
	Dim server As String, path As String
	server="SERVER"
	path="abc.nsf" 
	Dim db As NotesDatabase
	Set db=s.GetDatabase(server, path)
	Set GetConfigDoc=db.Getview("vwConfig").Getdocumentbykey(key, True)	
End Function
Sub Initialize
	Dim doc As NotesDocument
	Set doc=GetConfigDoc("No")
	Print doc.Values(0)
End Sub
例子2
Dim entry As NotesViewEntry 
Set entry=nav.Getchild(personEntry) 'nav is a NotesViewNavigator
Dim i As Integer 
For i=1 To entry.Siblingcount-1
	entry.Document.Type="Arrival"
	Call entry.Document.Save(True, False)
	Set entry=nav.Getnextsibling(entry)
Next

诸君目测一下上述两段代码运行结果如何?
实际结果是例子1中的代码会报错Object variablenot set。例子2中的代码运行后完全没作用。如果在工作中遇到了这样的情况,你可能会抓破脑袋,最后只能悻悻地换一种写法。语法没有任何错误,更让人懊恼的是在调试状态下逐行运行也看不出什么异常。原因就出在LotusScript的垃圾回收。

我们再来看一段代码:
Set view = db.GetView("$all") 
Set doc = view.GetFirstDocument() 
While Not (doc Is Nothing) 
	' do something... 
	Set doc = view.GetNextDocument(doc) 
Wend

这是我们遇到过无数次的片段,看上去很简单。即使有50,000条文档,程序也能运行得很稳定,占用的资源不会比50条文档的时候多。我们来仔细看看,在while的循环体中,doc变量被不断指向新的文档对象,旧的文档对象哪里去了?这不正是自动垃圾回收应用的场合。LotusScript解释器自动销毁了这些对象,对程序员来说完全是透明的。一切都很好,对吧?那为什么例子1和例子2的结果会不正常呢?这是因为LotusScript的垃圾回收有以下几个特点:
1.每一行代码运行完之后都会进行,以检查是否有对象不再有变量引用。函数退出时则回收所有本地变量指向的对象。
2.某个对象只要不再有局部变量直接指向它,就会被回收,即使有其他对象通过属性关联到它。
3.回收一个对象时,它包含的其他对象都会被回收。包含指的是一个对象由另一个对象的方法或属性获得,比如从一个数据库对象获取某个视图,从视图取得文档,从文档得到域对象。因为Notes对象的“树”状关系,当一个数据库对象被销毁时,属于它的视图、文档等各种对象都会被回收。而当一个NotesSession被回收时,此次会话涉及的所有对象也都会被销毁。
可以看出这样的垃圾回收机制简单有效,但是做得过分了,特别是第一点和第三点同时发挥作用。现在我们就可以解释例子1和例子2的实际运行结果。在例子1里,函数GetConfigDoc返回一个文档对象,但是随即包含这个文档的数据库对象就被回收,根据上面的第三点特性,该文档对象也被回收,因而文档变量无法被访问。在例子2里,NotesViewEntry指向的文档对象修改这一行执行完之后,垃圾回收运行,因为没有变量指向,在下一行调用保存之前,该文档对象就被销毁了。调用保存方法时,文档对象是重新从NotesViewEntry获得的。
在实际编程中,我们还有可能不小心掉进这个过分强大的垃圾回收的陷阱里。幸好,了解了它的特点后,很容易就可以避开,只要稍微改动一下代码。对于例子1的情况,我们可以直接用函数返回配置文档中的字段值比如字符串和数字,或者在调用它的函数中,先创建一个数据库变量指向配置数据库。在例子2里,我们不能省去声明一个文档变量的步骤,这样因为有变量引用,文档对象就不会被回收:

Dim doc As NotesDocument
Dim entry As NotesViewEntry 'a record entry
Set entry=nav.Getchild(personEntry) 'the first record
Dim i As Integer 
For i=1 To entry.Siblingcount-1
	Set doc=entry.Document
	doc.Type="Arrival"
	Call doc.Save(True, False)
	Set entry=nav.Getnextsibling(entry)
Next

最后值得提出的是,上述三条垃圾回收的原则执行时有一个例外,那就是当前的NotesSession和当前数据库的NotesDatabase对象不会在程序运行中被销毁,比如上面的GetConfigDoc函数如果获取的是当前数据库的配置文档,那么函数退出时数据库对象不会被回收,配置文档对象也能够成功返回。这两种对象直到当前LotusScript程序退出时,如单机一个按钮触发的代码执行完毕,或者一个代理运行结束,才会被销毁。这个例外是合理也可以解释的,在一个LotusScript程序退出前,表示当前会话的NotesSession和当前数据库的NotesDatabase的资源一直必然有效,不应该也不可能被回收。

你可能感兴趣的:(script)