背景:
公司的一個(gè)web應(yīng)用,提交給測試部門做壓力測試(由于不是我負(fù)責(zé)的,不清楚如何做的壓力測試,以及測試指標(biāo)),結(jié)果沒壓多久,就出現(xiàn)OutOfMemory.
接手協(xié)助原因查找,通過監(jiān)控工具,發(fā)現(xiàn)
StandardSession(org.apache.catalina.session.
StandardSession)對象不斷增長,毫無疑問,肯定是在不斷創(chuàng)建Session對象.
備注:
一般做壓力測試,每次請求都不會指定JESSESIONID值,導(dǎo)致Web容器認(rèn)為每次請求都是新的請求,于是創(chuàng)建Session對象.
同事負(fù)責(zé)代碼Review,發(fā)現(xiàn)應(yīng)用沒有任何一個(gè)地方存放Session內(nèi)容.困惑之...
問題:Tomcat容器何時(shí)創(chuàng)建Session對象?
想當(dāng)然認(rèn)為,只有動態(tài)存放Session內(nèi)容的時(shí)候,才會創(chuàng)建Session對象.但是事實(shí)真得如此嗎?
先看Servlet協(xié)議描述:
請看:
getSession(boolean create)方法:
javax.servlet.http.HttpServletRequest.getSession(boolean create)
Returns the current HttpSession associated with this request or, if if there is no current session and create is true, returns a new session.
If create is false and the request has no valid HttpSession, this method returns null.
To make sure the session is properly maintained, you must call this method before the response is committed.
簡單地說:當(dāng)create變量為true時(shí),如果當(dāng)前Session不存在,創(chuàng)建一個(gè)新的Session并且返回.
getSession()方法:
javax.servlet.http.HttpSession getSession();
Returns the current session associated with this request, or if the request does not have a session, creates one.
簡單的說:當(dāng)當(dāng)前Session不存在,創(chuàng)建并且返回.
所以說,協(xié)議規(guī)定,在調(diào)用getSession方法的時(shí)候,就會創(chuàng)建Session對象.
既然協(xié)議這么定了,我們再來看看Tomcat是如何實(shí)現(xiàn)的:(下面的描述,是基于Tomcat6.0.14版本源碼)
先看一張簡單的類圖:
ApplicationContext:Servlet規(guī)范中ServletContext的實(shí)現(xiàn)
StandardContext:Tomcat定義的Context默認(rèn)實(shí)現(xiàn).維護(hù)了一份SessionManager對象,管理Session對象.所有的Session對象都存放在Manager定義的Map<String,Session>容器中.
StanardManager:標(biāo)準(zhǔn)的Session管理,將Session存放在內(nèi)容,Web容器關(guān)閉的時(shí)候,持久化到本地文件
PersistentManager:持久化實(shí)現(xiàn)的Session管理,默認(rèn)有兩種實(shí)現(xiàn)方式:
--持久化到本地文件
--持久化到數(shù)據(jù)庫
了解了大概的概念后,回頭再來看看org.apache.catalina.connector.
Request.getSession()是如何實(shí)現(xiàn)的.
最終調(diào)用的是doGetSession(boolean create)方法,請看:
protected Session doGetSession(boolean create) {
// There cannot be a session if no context has been assigned yet
if (context == null)
return (null);
// Return the current session if it exists and is valid
if ((session != null) && !session.isValid())
session = null;
if (session != null)
return (session);
// Return the requested session if it exists and is valid
Manager manager = null;
if (context != null)
manager = context.getManager();
if (manager == null)
return (null); // Sessions are not supported
if (requestedSessionId != null) {
try {
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
if ((session != null) && !session.isValid())
session = null;
if (session != null) {
session.access();
return (session);
}
}
// Create a new session if requested and the response is not committed
if (!create)
return (null);
if ((context != null) && (response != null) &&
context.getCookies() &&
response.getResponse().isCommitted()) {
throw new IllegalStateException
(sm.getString("coyoteRequest.sessionCreateCommitted"));
}
// Attempt to reuse session id if one was submitted in a cookie
// Do not reuse the session id if it is from a URL, to prevent possible
// phishing attacks
if (connector.getEmptySessionPath()
&& isRequestedSessionIdFromCookie()) {
session = manager.createSession(getRequestedSessionId());
} else {
session = manager.createSession(null);
}
// Creating a new session cookie based on that session
if ((session != null) && (getContext() != null)
&& getContext().getCookies()) {
Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME,
session.getIdInternal());
configureSessionCookie(cookie);
response.addCookieInternal(cookie, context.getUseHttpOnly());
}
if (session != null) {
session.access();
return (session);
} else {
return (null);
}
}
至此,簡單地描述了Tomcat Session創(chuàng)建的機(jī)制,有興趣的同學(xué)要深入了解,不妨看看Tomcat源碼實(shí)現(xiàn).
補(bǔ)充說明,順便提一下Session的過期策略.
過期方法在:
org.apache.catalina.session.
ManagerBase(StandardManager基類) processExpires方法:
public void processExpires() {
long timeNow = System.currentTimeMillis();
Session sessions[] = findSessions();
int expireHere = 0 ;
if(log.isDebugEnabled())
log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
for (int i = 0; i < sessions.length; i++) {
if (sessions[i]!=null && !sessions[i].isValid()) {
expireHere++;
}
}
long timeEnd = System.currentTimeMillis();
if(log.isDebugEnabled())
log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
processingTime += ( timeEnd - timeNow );
}
其中,Session.isValid()方法會做Session的清除工作.
在org.apache.catalina.core.ContainerBase中,會啟動一個(gè)后臺線程,跑一些后臺任務(wù),Session過期任務(wù)是其中之一:
protected void threadStart() {
if (thread != null)
return;
if (backgroundProcessorDelay <= 0)
return;
threadDone = false;
String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
thread = new Thread(new ContainerBackgroundProcessor(), threadName);
thread.setDaemon(true);
thread.start();
}
OVER :)