最近有个朋友问我:
引用
tomcat的session存在哪里?
答:内存中。又问:
引用
那既然都保存在内存中,要清除过期的sesion时,又怎么能区分是哪个应用的session而不会清除错误?
答:(当时没看过这块的代码),说由于每个应用对应一个Session管理,此处应该有类似于数组之类的东西,将该应用的session管理起来,当该应用移除或者某些sesion过期时可以准确的删除掉。最近看了下代码,对比下源码,发现回复的还行,没有特别大的出入。
我们的应用中一般都会用到session,那这个session在应用服务器内部到底是怎么保存到处理的呢?
当我们请求一个应用时,如果页面中用到了session,此时的创建session的调用链如下:
Daemon Thread [http-bio-8090-exec-1] (Suspended (breakpoint at line 145 in SessionIdGenerator))
owns: SocketWrapper<E> (id=234)
SessionIdGenerator.generateSessionId() line: 145
StandardManager(ManagerBase).generateSessionId() line: 807
StandardManager(ManagerBase).createSession(String) line: 653
Request.doGetSession(boolean) line: 2892
Request.getSession(boolean) line: 2315
RequestFacade.getSession(boolean) line: 898
RequestFacade.getSession() line: 910
PageContextImpl._initialize(Servlet, ServletRequest, ServletResponse, String, boolean, int, boolean) line: 146
PageContextImpl.initialize(Servlet, ServletRequest, ServletResponse, String, boolean, int, boolean) line: 125
JspFactoryImpl.internalGetPageContext(Servlet, ServletRequest, ServletResponse, String, boolean, int, boolean) line: 112
JspFactoryImpl.getPageContext(Servlet, ServletRequest, ServletResponse, String, boolean, int, boolean) line: 65
index.jsp line: not available
index_jsp(HttpJspBase).service(HttpServletRequest, HttpServletResponse) line: 70
index_jsp(HttpServlet).service(ServletRequest, ServletResponse) line: 728
JspServletWrapper.service(HttpServletRequest, HttpServletResponse, boolean) line: 432
JspServlet.serviceJspFile(HttpServletRequest, HttpServletResponse, String, boolean) line: 390
JspServlet.service(HttpServletRequest, HttpServletResponse) line: 334
JspServlet(HttpServlet).service(ServletRequest, ServletResponse) line: 728
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 305
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 210
StandardWrapperValve.invoke(Request, Response) line: 222
StandardContextValve.invoke(Request, Response) line: 123
NonLoginAuthenticator(AuthenticatorBase).invoke(Request, Response) line: 472
StandardHostValve.invoke(Request, Response) line: 171
ErrorReportValve.invoke(Request, Response) line: 99
AccessLogValve.invoke(Request, Response) line: 936
StandardEngineValve.invoke(Request, Response) line: 118
CoyoteAdapter.service(Request, Response) line: 408
Http11Processor(AbstractHttp11Processor<S>).process(SocketWrapper<S>) line: 1009
Http11Protocol$Http11ConnectionHandler(AbstractProtocol$AbstractConnectionHandler<S,P>).process(SocketWrapper<S>, SocketStatus) line: 589
JIoEndpoint$SocketProcessor.run() line: 310
ThreadPoolExecutor$Worker.runTask(Runnable) line: 895
ThreadPoolExecutor$Worker.run() line: 918
TaskThread(Thread).run() line: 662
以上调用链的部分关键代码:
/**
* Construct and return a new session object, based on the default
* settings specified by this Manager's properties. The session
* id specified will be used as the session id.
* If a new session cannot be created for any reason, return
* <code>null</code>.
*
* @param sessionId The session id which should be used to create the
* new session; if <code>null</code>, a new session id will be
* generated
* @exception IllegalStateException if a new session cannot be
* instantiated for any reason
*/
@Override
public Session createSession(String sessionId) {
if ((maxActiveSessions >= 0) &&
(getActiveSessions() >= maxActiveSessions)) {
rejectedSessions++;
throw new TooManyActiveSessionsException(
sm.getString("managerBase.createSession.ise"),
maxActiveSessions);
}
// Recycle or create a Session instance
Session session = createEmptySession();
// Initialize the properties of the new session and return it
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(this.maxInactiveInterval);
String id = sessionId;
if (id == null) {
id = generateSessionId(); //此处生成sessionID
}
session.setId(id);
sessionCounter++;
SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
synchronized (sessionCreationTiming) {
sessionCreationTiming.add(timing);
sessionCreationTiming.poll();
}
return (session);
}
public void setId(String id, boolean notify) {
if ((this.id != null) && (manager != null))
manager.remove(this);
this.id = id;
if (manager != null)
manager.add(this); //注意此处
if (notify) {
tellNew();
}
}
在setId时,同时在Manager中将相应的session保存了下来。此处对于session的保存使用的是ConcurrentHashMap,在创建后将其添加到map中,session过期后将其移除。
/**
* Add this Session to the set of active Sessions for this Manager.
*
* @param session Session to be added
*/
@Override
public void add(Session session) {
sessions.put(session.getIdInternal(), session);
int size = getActiveSessions(); //此处应该可以使用AtomicInteger替换掉。
if( size > maxActive ) {
synchronized(maxActiveUpdateLock) {
if( size > maxActive ) {
maxActive = size;
}
}
}
}
而在停止一个应用时,相应的remove session的调用链如下:
Daemon Thread [http-bio-8090-exec-2] (Suspended (breakpoint at line 733 in ManagerBase))
owns: StandardSession (id=340)
owns: StandardManager (id=326)
owns: StandardContext (id=327)
owns: SocketWrapper<E> (id=341)
StandardManager(ManagerBase).remove(Session, boolean) line: 733
StandardSession.expire(boolean) line: 840
StandardManager.doUnload() line: 463
StandardManager.unload() line: 353
StandardManager.stopInternal() line: 518
StandardManager(LifecycleBase).stop() line: 232
StandardContext.stopInternal() line: 5569
StandardContext(LifecycleBase).stop() line: 232
HTMLManagerServlet(ManagerServlet).stop(PrintWriter, ContextName, StringManager) line: 1306
HTMLManagerServlet.stop(ContextName, StringManager) line: 733
HTMLManagerServlet.doPost(HttpServletRequest, HttpServletResponse) line: 221
HTMLManagerServlet(HttpServlet).service(HttpServletRequest, HttpServletResponse) line: 647
HTMLManagerServlet(HttpServlet).service(ServletRequest, ServletResponse) line: 728
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 305
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 210
CsrfPreventionFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 213
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 243
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 210
SetCharacterEncodingFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 108
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 243
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 210
StandardWrapperValve.invoke(Request, Response) line: 222
StandardContextValve.invoke(Request, Response) line: 123
BasicAuthenticator(AuthenticatorBase).invoke(Request, Response) line: 581
StandardHostValve.invoke(Request, Response) line: 171
ErrorReportValve.invoke(Request, Response) line: 99
AccessLogValve.invoke(Request, Response) line: 936
StandardEngineValve.invoke(Request, Response) line: 118
CoyoteAdapter.service(Request, Response) line: 408
Http11Processor(AbstractHttp11Processor<S>).process(SocketWrapper<S>) line: 1009
Http11Protocol$Http11ConnectionHandler(AbstractProtocol$AbstractConnectionHandler<S,P>).process(SocketWrapper<S>, SocketStatus) line: 589
JIoEndpoint$SocketProcessor.run() line: 310
ThreadPoolExecutor$Worker.runTask(Runnable) line: 895
ThreadPoolExecutor$Worker.run() line: 918
TaskThread(Thread).run() line: 662
其中执行remove的代码如下:
/**
* Perform the internal processing required to invalidate this session,
* without triggering an exception if the session has already expired.
*
* @param notify Should we notify listeners about the demise of
* this session?
*/
public void expire(boolean notify) {
// Check to see if expire is in progress or has previously been called
if (expiring || !isValid)
return;
synchronized (this) {
// Check again, now we are inside the sync so this code only runs once
// Double check locking - expiring and isValid need to be volatile
if (expiring || !isValid)
return;
if (manager == null)
return;
... //省略部分代码
}
... //省略部分代码
if (ACTIVITY_CHECK) {
accessCount.set(0);
}
setValid(false);
// Remove this session from our manager's active sessions
manager.remove(this, true);
}
StandardManager中的remove方法:
/**
* Remove this Session from the active Sessions for this Manager.
*
* @param session Session to be removed
* @param update Should the expiration statistics be updated
*/
@Override
public void remove(Session session, boolean update) {
// If the session has expired - as opposed to just being removed from
// the manager because it is being persisted - update the expired stats
if (update) {
long timeNow = System.currentTimeMillis();
int timeAlive =
(int) (timeNow - session.getCreationTimeInternal())/1000;
updateSessionMaxAliveTime(timeAlive);
expiredSessions.incrementAndGet();
SessionTiming timing = new SessionTiming(timeNow, timeAlive);
synchronized (sessionExpirationTiming) {
sessionExpirationTiming.add(timing);
sessionExpirationTiming.poll();
}
}
if (session.getIdInternal() != null) {
sessions.remove(session.getIdInternal()); //注意此处,即为上面保存session的concurrentHashMap
}
}