Domino中Java编码与内存管理
1. 引言
可能很多开发人员在Domino平台上做过Java编码(如java agent、servlet、java Web Service提供者、DIIOP等)开发,与Lotusscript脚本语言相比,操作Domino数据对象的过程极为类似——获取session、上下文、数据库对象、视图对象、文档对象……,但有没有发现Java编码中,需要明确的语句对内存进行recycle,而Lotusscript不需要。原因何在?
2. 解答
对于一台硬件服务器来讲,要尽可能地发挥硬件性能,从编码角度来讲回收每一个对象是非常重要的。我们分别对Lotusscript 和 java两种方式创建对象的细节进行说明:
2.1 Lotusscript创建对象
Lotusscript创建对象时,其实它创建了两个对象——一个是Lotusscript对象,另一个是对应的C++对象(众所周知,Domino的实现是基于C++的)。然后,这个Lotusscript对象像“指针”一样指向了这个C++对象(真正的实现类操作的还是这个C++对象哟,Lotusscript对象其实是一个躯壳)。当LS代理执行完毕,这两个对象会自动销毁。假设Lotusscript与C++对象的这种自动同步被销毁的关系不健壮,C++对象没有被销毁,就是我们所说的内存泄露(ML = memory leak)
2.2 Java创建对象的过程
创建对象的过程与Lotusscript差不多一样。创建的Java对象也是指向了后端的C++对象。但我们知道原生的Java中的类(如Object、String、StringBuffer、Integer、Math、Date、Calendar、File等类)都自带内存回收机制,若我们把一个对象设置为null,Java的垃圾回收机制会自动进行内存回收。
可惜的是,当使用Java实现的Domino Dom对象(如View)时,如下面的语句:
View view = Db.getView("vwDocByDate_xm");
/* some code in here */
view = null;
这个view对象是Domino的对象,并非Java原生对象。可以想象成这是你当初学习Java时,写的Test类生成的objTest对象。当view被设置成null时(通常我们会认为已经将它标记为待被回收的对象),但这种操作并未将后端对应的那个C++对象标记为待回收状态。也就是说,显式地将java对象(view)打上回收标记,但它对C++对象不会起作用。
3. Domino DOM给出的解决方案
正因为以上原因,很多Domino平台上的开发人员,都觉得用Java实现业务功能是件危险的事情。因memory leak造成的服务器宕机的严重后果,成为了Developer在选择解决方案时的排斥Java编码的主要因素。
但有时候不得不使用Java时,该怎么办?如你需要输出复杂的excel文件,需要与第三方做系统集成,需要操作关系型数据库……,很难绕开Java编码这种场景。
其实,Domino平台上所有的类,在实现时都实现了一个名为recycle的方法
public void recycle()
public void recycle(java.util.Vector objects)
与此相关的内容,可以查找Domino Designer帮助文档的Supporting components章节中的recycle(是个链接)进行了解。
3.1 常规的内存回收代码
以下我给出简单的代码示例
public void myFunc(){
//Domino相关的类定义,在try块的外边
Session session = null;
Database db = null;
Document doc = null;
//以下是业务代码,写在try catch块中
try{
session = getSession();
db = session.getCurrentDatabase();
//...
//some code omitted
}catch(Exception e){
e.printStackTrace();
}finally{
//记得在程序的最后依次将Domino相关的对象回收
if(doc){
doc.recycle();
}
if(db){
db.recycle();
}
if(session){
session.recycle();
}
}
}
以述代码是常规情况下,编码中应该注意的内存回收要点。接下来,还有一些情况需要注意:
3.2 循环语句中获取对象
while(flag){
Database db = session.getDatabase("","dir/log.nsf");
// some code omitted ...
}
以上的写法是不推荐的,为db设置值的语句在while每执行一次就要执行一次,相当于是重复操作(虽然session.getDatabase("",“dir/log.nsf”)执行多次,但每一次进入循环时生成的db变量指向的是同一个对象,其实对内存产生不了太多占用)。但推荐的写法是
Database db = session.getDatabase("","dir/log.nsf");
while(flag){
// some code omitted ...
}
再考虑一下下边的情形,如果循环中数据库的名称是变化的:
Database db = null;
for(int i =0; i < arr.length; i ++){
db = session.getDatabase("",arr[i]);
// some code omitted ...
}
上边的情况就要注意了,需要在每次循环执行结束之前,回收内存(此时按3.1中finally中回收是会有问题的。原因是for循环每执行一次,语句session.getDatabase("",arr[i]);就会生成一个对象(由变量db指向),当db指向下一个数据库对象时,之前指向的内存块就是游离(没有变量可以找到它了)的,就造成了内存泄露。合适的写法是这样:
Database db = null;
for(int i =0; i < arr.length; i ++){
db = session.getDatabase("",arr[i]);
// some code omitted ...
//循环体的结尾记得回收对象所占用的内存
if(db){
db.recycle();
}
}
3.3 循环语句中生成对象
循环中生成对象,其实跟3.2的场景雷同,我直接给出建议的写法。
// some code omitted ...
for(int i =0; i < arr.length; i ++){
doc = db.createDocument();
// some code omitted ...
//循环体的结尾记得回收对象所占用的内存
if(doc){
doc.recycle();
}
}
4. 彩蛋
4.1 多态带来的便利
在3.1的代码中可以看到,如果Domino对象太多,要销毁的语句会非常冗繁,自身办呢?
话说Domino中所有的对象的父类为lotus.domino.Base,而且它和它的后代类中也都对recycle方法(有直接继承或重写)有实现。这样一来就可以通过Java的重载特性写一个通用的方法,以对所有的Domino对象销毁适用。方法定义如下:
public static void recycle_xm(lotus.domino.Base object){
if(object != null){
try{
object.recycle();
object = null;
}catch(Exception e){
e.printStackTrace();
}
}
}
如果定义了上边的这个方法,3.1 中的代码可以简化为
public void myFunc(){
//Domino相关的类定义,在try块的外边
Session session = null;
Database db = null;
Document doc = null;
//以下是业务代码,写在try catch块中
try{
session = getSession();
db = session.getCurrentDatabase();
//...
//some code omitted
}catch(Exception e){
e.printStackTrace();
}finally{
//记得在程序的最后依次将Domino相关的对象回收
recycle_xm(doc);
recycle_xm(db);
recycle_xm(session);
}
}
4.2 你的操作有误伤,你知道吗?
某项目需要通过Java方式访问Domino数据,某个方法中对视图All的数据进行了操作。
我的同事犯编写了相关的业务实现(如下示)。导致了运行时错误,消耗了大量头发,才查出的问题根源。
public void fnXXX(){
try{
View v1 = db.getView("All");
// some code emitted ...
View v2 = db.getView("All");
v1.recycle(); //此处我不使用v1变量了,就销毁
System.out.println("=================================");
System.out.println(v2.getName()); //此处使用v2变量时,就出错了
System.out.println("=================================");
}catch(Exception e){
e.printStackTrace();
}
}
为什么在语句System.out.println(v2.getName());时会出现如下的错误呢?因为是v1、v2在赋值时右侧的语句均为db.getView(“All”);这个语句不管执行多少遍,指向的内存块是同一块。所以v1、v2指向的是同一块内存地址。当v1.recycle()时,v2指向的对象就被销毁了,这种情况得注意注意啦!

(结束)
本文探讨了Domino中Java编码时的内存管理问题,对比了Lotusscript和Java创建对象的区别,强调了Java在Domino环境下的内存回收的重要性。文章详细介绍了如何避免内存泄漏,包括常规的内存回收代码、循环语句中的内存管理,并提供了多态优化的解决方案,以提高代码的效率和安全性。


被折叠的 条评论
为什么被折叠?



