歡迎您光臨本站 註冊首頁

為J2EE應用程序構建分散式對象管理框架

←手機掃碼閱讀     火星人 @ 2014-03-10 , reply:0

許多企業 Java™ 技術開發人員都會構建自己的對象管理基礎架構,以此改進應用程序性能.但是,對於跨多台物理機器上的分散式 JVM 運行的應用程序,傳統的對象池會遇到問題.在本文中,Zhengrong Tang 將提供一個對象管理框架,這個框架可以輕鬆地處理分散式系統.

為了提高 Java™ 應用程序的性能,許多開發人員都會開發自己的對象管理解決方案來替代默認的垃圾收集器.一種常用的解決方案是對象池(object pooling).創建 Java 對象是一種開銷很大的操作;如果只創建對象一次並多次重複使用它們,就會減少開銷.這對於伺服器端應用程序尤其有意義,因為通常會重複執行相同的伺服器代碼.對於在單一 Java 虛擬機(JVM)中運行的應用程序,這樣的解決方案通常很容易實現.但是,在多層的體系結構中,要跨多台機器和多個 JVM 管理對象實例,這會非常複雜.

在本文中,我將介紹一個在多層 J2EE 體系結構中管理 Java 對象的框架.這個框架基於 Servlet、JSP、EJB、JMS 和 Struts 技術定義的標準介面.因此,它並不依賴於任何特定廠商的解決方案.當不再需要對象實例時,其他許多對象管理解決方案要求應用程序顯式地釋放它們;但是這個框架並非如此,它使用範圍的概念自動地釋放對象實例.在禁用默認的垃圾收集器時內存泄漏是一個嚴重的問題,這個特性會顯著降低內存泄漏的風險.總之,這個框架為改進分散式 J2EE 環境中的性能提供了一個實用且易用的解決方案.

對象創建和池

Java 對象創建和垃圾收集都是開銷很大的操作.頻繁的對象創建是導致 Java 應用程序性能問題的主要原因之一.這個問題在 servlet 等伺服器端程序中影響更嚴重.在方法中常常有許多短期存在的本地變數,但是方法本身會被頻繁地反覆調用.因此,會頻繁地創建對象,然後通過垃圾收集銷毀它們.這種 object churning 現象會嚴重損害性能.

對象池 是這個問題的一種解決方案.其思想是創建一個對象集合(通常包含相同類型的對象)並把它們存儲在一個池中.如果需要一個對象,就從池中獲取它,而不需要重新創建它.當這個對象完成它的工作之後,它被返回給池,可供以後重新使用.對象池需要跟蹤每個對象的狀態,應該是線程安全的.可以找到許多 Java 對象池的實現,比如 Apache 的 Common Pool(參見 參考資料).如果不考慮實現細節,對象池一般採用清單 1 所示的介面.

清單 1. 對象池 API

public Object getObject(Class clazz);
public void returnObject(Object obj);

getObject() 方法從池中獲取給定類的一個實例.returnObject() 方法把這個對象釋放回池中.

對象池在大多數情況下都可以提高性能.但是,它並非沒有代價.,當不再需要對象時,開發人員必須顯式地調用 returnObject(object).這完全背離了自動垃圾收集機制的目標,而垃圾收集是 Java 語言的重要特性之一(可能是最重要的特性).第二,應用程序有出現內存泄漏的風險.如果不把對象顯式地返回池中,對象就會一直留著.這兩個問題使開發人員不太敢採用對象池技術.

如果 Java 開發人員可以享受對象池的性能優勢,同時不必為顯式釋放對象操心,那就好了.幸運的是,這在大多數伺服器端 J2EE 應用程序中是可行的.本文討論的框架會提供一個實用的解決方案.

一個基於範圍(scope)的解決方案

我們看看一個包含 servlet、JSP 頁面和 EJB 組件的伺服器端應用程序.圖 1 給出一種典型的設計,它使用 MVC 和 Façade 模式.

圖 1. 伺服器端應用程序

在收到請求時,servlet 調用會話 bean 上的一個方法,這個方法進而調用實體 bean 上的一個方法.在會話調用返回之後,servlet 把請求轉發給 JSP 頁面.在整個流程中,按照 圖 1 所示的方式創建對象.不同的對象有不同的壽命.一些對象只在一個請求的處理周期中存在;請求完成之後,就不再需要這些對象了.其他對象的壽命可能更長:例如,它們可能在一個 HTTP 會話甚至整個應用程序範圍內存在.如果標識出每個對象的範圍,就可以指定在這個範圍結束時自動釋放對象.例如,圖中的 object1 和 object4 在請求範圍內存在.請求完成之後,可以自動釋放這兩個對象.object2 在事務範圍內存在.在事務終止之後,可以自動釋放它.object3 在應用程序範圍內存在,因此在應用程序的整個生命周期內都存活.

為了實現這種效果,要解決兩個問題:,修改對象池,讓它理解範圍的概念.第二,建立一種自動通知機制,從而在範圍結束時發出通知.

ObjectManager API

在本文中,我將用對象管理器(object manager) 這個詞表示這個框架,以便區別於傳統的對象池.與對象池一樣,對象管理器也負責從池中獲取對象和把對象返回池中.另外,對象管理器還理解範圍的概念.清單 2 給出對象管理器的 API.

清單 2. ObjectManager API

public interface ObjectManager {
/**
* Retrieve an object instance of the give class from the object pool
* for the given scope, identified by a scope type and a scope key.
*
* @param clazz the class
* @param scopeType the type of the scope
* @param scopeKey the key to identify the scope
*
* @return an object instance retrieved from the object pool
*/
public Object getObject(Class clazz, int scopeType, Object scopeKey);


/**
* Release an object back to the object pool.
*
* @param object the object to be released
*/
public void releaseObject(Object object);

/**
* Release all objects of the given scope, identified by a scope type
* and a scope key.
* @param scopeType the type of the scope that objects bound to
* @param scopeKey the key to identify the scope
*/
public void releaseAll(int scopeType, Object scopeKey);

}

為了從對象池獲取對象,需要提供一個範圍類型和一個範圍鍵.對象管理器把範圍信息與獲取的每個對象關聯起來.在調用 releaseAll() 方法時,對象管理器可以識別出與給定的範圍相關聯的對象,並把它們釋放回池中.

範圍類型

大多數伺服器端 J2EE 應用程序中有六種常用的範圍類型:

Transaction(事務)

Request(請求)

HTTP session(HTTP 會話)

Application(應用程序)

Global(全局)

None(無)

開發人員可以根據應用程序的需要增加其他範圍類型.清單 3 定義本文中討論的範圍類型.

清單 3. 範圍類型定義

public class ScopeTypes {

public final static int TRANSACTION = 1;
public final static int REQUEST = 2;
public final static int HTTP_SESSION = 3;
public final static int APPLICATION = 4;
public final static int GLOBAL = 5;
public final static int NONE = 6;

}

事務範圍

事務範圍覆蓋一個事務的整個生命周期.這個範圍開始於一個事務的開始.這時會創建一個惟一的範圍鍵.這個範圍結束於提交或回滾事務時.這時,與事務範圍相關聯的所有對象被自動釋放回它們的池.

請求範圍

請求範圍與一個 servlet 請求的範圍對應;在容器調用 servlet 來處理請求之後,請求範圍立即開始.同時會創建一個惟一的範圍鍵.在 servlet 完成處理之前請求範圍結束.這時,與這個範圍相關聯的所有對象被自動釋放回它們的池.

HTTP 會話範圍

HTTP 會話範圍與一個 HTTP 會話的生命周期對應.它從創建一個新的 HttpSession 時開始.這時會創建一個惟一的範圍鍵.它結束於會話被銷毀或過期時.這時,與這個範圍相關聯的所有對象被自動釋放回它們的池.

應用程序範圍

應用程序範圍覆蓋應用程序的整個生命周期.它開始於把一個應用程序部署到應用伺服器時.這時會創建一個惟一的範圍鍵.這個範圍結束於應用程序停止運行或從應用伺服器中刪除時.這時,與這個範圍相關聯的所有對象被自動釋放回它們的池.

全局範圍

全局範圍是最大的範圍.採用這種範圍的對象不會被釋放.

無範圍

無範圍用於不使用對象池的對象.採用這種範圍的對象每次都通過自己的對象構造函數來創建,並由 Java 垃圾收集器釋放.對象管理器根本不管理它們.

範圍鍵和事件通知

為了使用 前一節 中定義的對象管理器和標識範圍,需要使用範圍鍵.在本節中,討論如何創建範圍鍵以及如何存儲它們.讓對象管理器理解範圍的概念,就可以解決本文開頭提到的一個問題.另一個問題是如何根據對象的範圍自動釋放對象.為了讓開發人員不必顯式地釋放對象,需要在範圍結束時通知對象管理器,讓它能夠正確地釋放對象.servlet 和 EJB 容器提供了處理範圍變化所需的事件觸發機制.我們來看看如何使用事件觸發機制處理各種類型的範圍.

事務範圍

根據 EJB 2.0 規範,實現 javax.ejb.SessionSynchronization 的會話 bean 類必須實現 afterBegin()、beforeCompletion() 和 afterCompletion().在事務開始之後,EJB 容器馬上調用 afterBegin();在事務完成之前,它會調用 beforeCompletion().在事務完成或回滾之後,EJB 容器馬上調用 afterCompletion().

使用 afterBegin() 為事務範圍創建惟一的範圍鍵.把範圍鍵存儲在會話 bean 中,供以後獲取.

使用 afterCompletion() 通知對象管理器事務範圍已經結束,應該釋放對應的對象.

請求範圍

根據所用技術的不同,可以以不同方式為請求範圍生成事件.本節討論目前最流行的四種技術.

Servlet 2.4. Java Servlet 規範的 2.4 版增加了與請求相關的事件添加了一個JianTingQi.javax.servlet.ServletRequestListener 包含 requestInitialized() 方法和 requestDestroyed() 方法.

requestInitialized() —— 當一個 servlet 請求即將進入範圍時,容器調用這個方法.使用它為請求範圍創建惟一的範圍鍵.把範圍鍵存儲在請求對象本身中,供以後獲取.

requestDestroyed() —— 當一個 servlet 請求即將離開範圍時,容器調用這個方法.使用它通知對象管理器請求範圍即將結束,應該釋放對應的對象.

Servlet 2.3 或更老的版本.Java Servlet 規範的 2.3 版沒有為與請求相關的事件提供任何監聽功能.克服這個缺陷的最好方法是,在一個擴展自 javax.servlet.http.HttpServlet 的超類中創建範圍鍵並實現通知邏輯.然後,就可以從這個超類派生所有應用程序 servlet.應該在這個超類的 service() 方法中創建範圍鍵並實現通知邏輯.

Struts 1.0.與 Servlet 2.3 一樣,在 Struts 1.0 中應該在一個擴展自 org.apache.struts.action.ActionServlet 的超類中創建範圍鍵並實現通知邏輯.可以使用 process() 方法創建範圍鍵並實現通知邏輯.

Struts 1.1.從 Struts 1.1 開始,在每次從容器接收 servlet 請求時 ActionServlet 執行的處理邏輯中,添加了 org.apache.struts.action.RequestProcessor.使用 RequestProcessor.process() 方法創建範圍鍵並實現通知邏輯.

HTTP 會話範圍

根據 Servlet 2.3 規範,實現 javax.servlet.http.HttpSessionListener 的JianTingQi需要實現 sessionCreated() 和 sessionDestroyed().

sessionCreated() —— 在創建新會話之後,容器調用這個方法.使用它為會話範圍創建惟一的範圍鍵.把範圍鍵存儲在會話對象本身中,供以後獲取.

sessionDestroyed() —— 在會話失效或過期之後,容器調用這個方法.使用它通知對象管理器會話範圍已經結束(或即將結束),應該釋放對應的對象.(在 Servlet 2.4 規範中,當會話即將失效時,還會調用另一個方法;這個更新並不影響這個框架).

應用程序範圍

根據 Servlet 2.3 規範,實現 javax.Servlet.ServletContextListener 的JianTingQi需要實現 contextInitialized() 和 contextDestroyed().

contextInitialized() —— 當應用程序完成初始化並準備提供服務時,容器調用這個方法.使用它為應用程序範圍創建惟一的範圍鍵.把範圍鍵存儲在 servlet 上下文對象本身中,供以後獲取.

contextDestroyed() —— 當應用程序即將停止運行時,容器調用這個方法.使用它通知對象管理器應用程序範圍已經結束,應該釋放對應的對象.

全局範圍

這個範圍不需要事件,因為與它相關聯的對象不會被釋放.

無範圍

這個範圍不需要事件,因為與它相關聯的對象由垃圾收集器釋放,而不是由對象管理器負責.

示例

在本節中,我通過一個示例進一步解釋前面介紹的概念.示例代碼演示如何用對象管理器在一個 Struts 1.1 應用程序中管理請求範圍.

清單 4 所示的 MyRequestProcessor 攔截每個 Struts 動作請求,在處理每個請求之前創建請求範圍鍵,在處理請求之後釋放對象.

清單 4. RequestProcessor 類的實現

public class MyRequestProcessor extends RequestProcessor{
public void
process(HttpServletRequest request, HttpServletResponse response)
throws ServletException {


// before each request is processed, create a request key
String requestScopeKey = UniqueIDGenerator.GetID();
request.setAttribute("requestScopeKey ", requestScopeKey);

try {

// process the request
super.process(request, response);

} finally {

// after the request is processed, release all objects
// bound to this request scope
ObjectManager objMgr = ObjectManagerFactory.GetInstance();
objMgr.releaseAll(ScopeTypes.REQUEST, requestScopeKey);
}
}

}

在清單 5 中,AnyAction 代表任何 Struts 動作;它演示如何從對象管理器獲取對象.

清單 5. 動作類的實現

public class AnyAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) {

// ...

String requestScopeKey = request.getAttribute("requestScopeKey");
ObjectManager objMgr = ObjectManagerFactory.GetInstance();

obj = objMgr.getObject(obj.getClass(), ScopeTypes.REQUEST,
requestScopeKey);

// ...
}

}

只要範圍處於同一個組件或容器中,前面討論的所有概念就都是有效的.在一般情況下,這個條件是成立的.例如,請求範圍的開始和結束都在一個 servlet 容器中.事務範圍的開始和結束都在一個 EJB 容器中.如果這種設計適合您的需要,您就不需要閱讀本文餘下的內容了.但是,在許多情況下,範圍可能跨多個組件.例如,假設一個 Struts 動作中總是調用一個 EJB 會話 bean 方法.這個會話 bean 方法中的一些對象可能屬於一個 HTTP 請求.也就是說,HTTP 請求開始於一個 Struts 動作,然後延續到一個 EJB 會話 bean.在下面幾節中,討論這種分散式環境中的問題.

分散式對象管理器

典型的 J2EE 應用程序可能有多個組件,這些組件在不同的 JVM 甚至不同的物理機器上運行.在集群環境中,一個邏輯組件可能由多個物理節點組成,每個節點在一個 JVM 上運行.在這種分散式計算環境中(見圖 2),每個 JVM 至少有一個對象管理器.

圖 2. 分散式計算環境

不同的對象管理器可能管理屬於同一範圍的對象.例如,圖 2 中的 object1、object2、object3 和 object4 都屬於同一個請求範圍.在這個請求範圍結束之後,它們各自的對象管理器必須釋放它們.因此,通知機制的工作範圍不能只限於一個 JVM,必須跨多個 JVM.可以使用 Java Messaging Service(JMS)實現這個目標.

使用 JMS 進行通信

如果必須將一個事件同時通知多個對象管理器,那麼使用發布-訂閱模型是最合適的.JMS 支持發布-訂閱模型,它使用主題進行消息廣播.為了使用 JMS,需要把每個對象管理器與一個 JMS JianTingQi關聯起來.如圖 3 所示,當一個範圍結束時,一個消息被發布到一個主題.這個消息包含一個範圍類型和一個範圍鍵.然後,所有訂閱這個主題的 JMS JianTingQi會檢查這個消息,並要求它們的對象管理器釋放與這個範圍相關聯的對象.

圖 3. 使用 JMS 協調對象管理

為了更詳細地了解這種機制的工作方式,我們看看示例代碼.清單 6 演示如何廣播包含範圍信息的消息.

清單 6. 廣播消息的實現示例

// ---------------------------------------------------
// a scope is ended at this point
// the scope type is stored in scopeType
// the scope key is stored in scoreKey
// ctx is a JNDI context


TopicConnectionFactory tcf = (TopicConnectionFactory)
ctx.lookup("ObjectManagerJMSConnection");
TopicConnection tc = tcf.createTopicConnection();
tc.start();
TopicSession ts = tc.createTopicSession(false, 1);
Topic t = (Topic) ctx.lookup("ObjectManagerJMSTopic");
TopicPublisher tp = ts.createPublisher(t);

TextMessage tm = ts.createTextMessage();
tm.setText(scopeType ":" scopeKey);
tp.publish(tm);

清單 6 給出發布 JMS 消息的標準方法.TopicConnectionFactory 和 Topic 的名稱應該反映 JMS 伺服器中設置的名稱.消息是一個包含範圍信息的文本消息.

清單 7 演示如何實現與對象管理器相關聯的 JMS JianTingQi.

清單 7. MessageListener 類實現

public class ObjectManagerListener implements MessageListener {


private Context ctx = null;
private TopicConnection tcon = null;
private TopicSession tsession = null;
private TopicSubscriber tsubscriber = null;

/**
* Constructor.
*
* @param ctx a JNDI context
* @throws NamingException
*/
public ObjectManagerListener(Context ctx) throws NamingException{
this.ctx = ctx;
}


/**
* Starts the listener.
*
* @throws Exception
*/
public void start() throws Exception {
TopicConnectionFactory tconFactory =
(TopicConnectionFactory) ctx.lookup("ObjectManagerJMSConnection");
tcon = tconFactory.createTopicConnection();
tsession = tcon.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = (Topic) ctx.lookup("ObjectManagerJMSTopic");
tsubscriber = tsession.createSubscriber(topic);
tsubscriber.setMessageListener(this);
tcon.start();
}

/**
* Stops the listener.
*
* @throws Exception
*/
public void stop() throws Exception {
this.close();
}

/**
* Upon receiving a message, the listener asks its object manager
* to release objects in the scope specified in the message.
*
* @param msg A message containing a scope type and a scope key.
*/
public void onMessage(Message msg){
try {
String msgText;
if (msg instanceof TextMessage) {
msgText = ((TextMessage)msg).getText();
} else {
msgText = msg.toString();
}

StringTokenizer st = new StringTokenizer(msgText, ":");
if (st.countTokens() == 2) {
String scopeType = st.nextToken();
String scopeKey = st.nextToken();
ObjectManager objMgr = ObjectManagerFactory.GetInstance();
objMgr.releaseAll(scopeType, scopeKey);
}
} catch (Exception e) {
e.printStackTrace();
}
}


public void close() throws Exception {
tsubscriber.close();
tsession.close();
tcon.stop();
tcon.close();
ctx = null;
}

}

ObjectManagerListener 是一個 JMS JianTingQi.啟動之後,它連接到 JMS 伺服器並監聽主題.注意,在 start() 方法中,TopicConnectionFactory 和 Topic 的名稱應該反映 JMS 伺服器中設置的名稱.在收到文本消息時,如果消息的格式是正確的,ObjectManagerListener 就從消息中提取出範圍類型和範圍鍵,並要求對象管理器釋放與這個範圍相關聯的所有對象.

用來傳遞範圍信息的上下文

在分散式環境中,範圍跨多個組件.因此,在一個組件中創建的範圍鍵必須被傳遞給其他組件,讓這些組件可以使用鍵獲取與這個範圍相關聯的對象.跨多個組件傳遞範圍鍵的惟一方法是通過方法調用.為了幫助實現這種方式,需要用一個上下文對象存儲範圍信息.對於每個請求,創建一個上下文對象.這個對象存儲當前請求範圍、HTTP 會話範圍和應用程序範圍的鍵.然後,通過方法調用把這個對象傳遞給其他組件.當一個事務開始時,這個事務的範圍鍵被添加到這個上下文對象中.當事務結束時,刪除對應的範圍鍵.清單 8 定義這個對象的 API.

清單 8. RequestContext API

public interface RequestContext {
/**
* Get the scope key for the given scope type.
*
* @param scopeType
* @return
*/
public Object getScopeKey(int scopeType);

/**
* Set the scope key for the given scope type.
*
* @param scopeType
* @param scopeKey
*/
public void setScopeKey(int scopeType, Object scopeKey);
}

因為需要跨組件傳遞上下文對象,這個對象應該是可序列化的.

修改後的示例

現在可以用 JMS 和 RequestContext 修改前面的示例.除了創建請求範圍鍵之外,在處理請求之前,MyRequestProcessor 需要獲得應用程序範圍鍵和會話範圍鍵,並把它們存儲在 RequestContext 對象中.在處理請求之後,它發送一個 JMS 消息,表示這個請求範圍已經結束.清單 9 演示實現方法.

清單 9. 修改後的 RequestProcessor 類實現

public class MyRequestProcessor extends RequestProcessor{
public void
process(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {

// assume when application started, the application id was generated
// and stored in ServletContext with attribute name " appScopeKey"
String appScopeKey =
request.getServletContext().getAttribute("appScopeKey");

// session scope key
String sessionScopeKey = request.getSession().getId();

// request scope key
String requestScopeKey = UniqueIDGenerator.GetID();

RequestContext ctx = RequestContextFactory.getInstance();
ctx.setScopeKey(ScopeType.APPLICATION, appScopeKey);
ctx.setScopeKey(ScopeType.HTTP_SESSION, sessionScopeKey);
ctx.setScopeKey(ScopeType.REQUEST, requestScopeKey);

// store the request context in request
request.setAttribute("requestContext", ctx);

try {

// process the request
super.process(request, response);

} finally {

// after the request is processed, release all objects bound
// to this request scope
TopicConnectionFactory tcf = (TopicConnectionFactory)
ctx.lookup("ObjectManagerJMSConnection");
TopicConnection tc = tcf.createTopicConnection();
tc.start();
TopicSession ts = tc.createTopicSession(false, 1);
Topic t = (Topic) ctx.lookup("ObjectManagerJMSTopic");
TopicPublisher tp = ts.createPublisher(t);


TextMessage tm = ts.createTextMessage();
tm.setText(ScopeTypes.REQUEST ":" requestScopeKey);
tp.publish(tm);

}
}

}

修改後的 AnyAction 類(清單 10)演示如何從 RequestContext 對象獲取請求範圍鍵並把 RequestContext 對象傳遞給 EJB 組件.

清單 10. 修改後的 Action 類實現

public class AnyAction extends Action{
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) {

// ...

RequestContext ctx =
(RequestContext) request.getAttribute("requestContext");

// get the request scope key from the context object
String scopeKey = ctx.getScopeKey(ScopeType.REQUEST);
ObjectManager objMgr = ObjectManagerFactory.GetInstance();
obj =
objMgr.getObject(obj.getClass(), ScopeTypes.REQUEST, scopeKey);

// call methodY of sessionBeanX and pass the context object
// as an parameter
sessionBeanX.methodY(params, ctx);

// ...
}
}

清單 10 中的示例代碼並不像 清單 5 那樣從 HttpServletRequest 獲取範圍鍵,而是從 HttpServletRequest 獲取 RequestContext 對象.然後,從 RequestContext 對象獲取範圍鍵.在調用 EJB 對象的遠程或本地介面時,通過參數傳遞 RequestContext 對象.

我還添加了 SessionBeanX(清單 11),演示如何在 EJB 組件中使用 RequestContext 對象.

清單 11. SessionBean 類實現

public class SessionBeanX implements SessionBean{

public void methodY (Object params, RequestContext ctx) {

// ...

// get the request scope key from the context object
String scopeKey = ctx.getScopeKey(ScopeType.REQUEST);
ObjectManager objMgr = ObjectManagerFactory.GetInstance();
obj = objMgr.getObject(obj.getClass(), ScopeTypes.REQUEST, scopeKey);

// ...

}

}

為了獲取範圍信息,SessionBeanX 類的每個方法必須有 RequestContext 參數.如 清單 10 所示,當 EJB 客戶機調用遠程或本地介面時,它必須傳遞 RequestContext 對象,這個對象包含關於當前範圍的信息.然後,bean 方法中的 RequestContext 對象可以獲得當前範圍的鍵 —— 例如,通過 methodY().

結束語

本文介紹了一個用於在 J2EE 應用程序中管理對象實例的框架.這個框架的主要優點是,它使用在 J2EE 中定義的事件觸發機制自動地釋放對象實例.這不僅使開發人員不必在代碼中顯式地釋放對象,還可以減少內存泄漏的風險.本文的前半部分針對伺服器端代碼在單一 JVM 中運行的系統.後半部分針對伺服器端代碼在多個 JVM 中運行的環境.在本文中,我通過示例代碼解釋了概念.性能基準測試超出了本文的範圍,將在以後的文章中討論.


[火星人 ] 為J2EE應用程序構建分散式對象管理框架已經有1011次圍觀

http://coctec.com/docs/java/show-post-61960.html