1.问题出现
应用部署后,内存会随着使用时长边长,内存逐渐被吃掉,直到最后爬不动。开始研究一下吧......
2.问题分析
首先,dump下来java应用看看到底哪里占用了那么多内存:
看到了吧,这个DefaulWebSessionManager居然占用了84%的内存,仔细看一下,主要是MemorySessionDAO中的ConcurrentHashMap中,这个map中为什么这么多的SimpleSession呢:
相关的继承关系如下:
DefaultWebSessionManager --> DefaultSessionManager --> AbstractValidatingSessionManager
其中sessionDAO在DefaultSessionManager中声明:
在这个MemorySessionDAO中维护了ConcurrentHashMap。其维护的机制如下:
- 每当请求通过shiro进行验证时候,均会创建一个SimpleSession,并放到这个ConcurrentHashMap中。
- AbstractValidatingSessionManager中的两个字段控制了上述map中session的过期并从map中删除
public AbstractValidatingSessionManager() { this.sessionValidationSchedulerEnabled = true; this.sessionValidationInterval = DEFAULT_SESSION_VALIDATION_INTERVAL; }
在AbstractValidatingSessionManager中一个关键的地方是设定了一个scheduler专门负责处理过期的session。
protected SessionValidationScheduler createSessionValidationScheduler() {
ExecutorServiceSessionValidationScheduler scheduler;
if (log.isDebugEnabled()) {
log.debug("No sessionValidationScheduler set. Attempting to create default instance.");
}
scheduler = new ExecutorServiceSessionValidationScheduler(this);
scheduler.setInterval(getSessionValidationInterval());
if (log.isTraceEnabled()) {
log.trace("Created default SessionValidationScheduler instance of type [" + scheduler.getClass().getName() + "].");
}
return scheduler;
}
看到了吧,就是这个ExecutorServiceSessionValidationScheduler。看看这个scheduler里面做了什么:
public void run() {
if (log.isDebugEnabled()) {
log.debug("Executing session validation...");
}
long startTime = System.currentTimeMillis();
this.sessionManager.validateSessions();
long stopTime = System.currentTimeMillis();
if (log.isDebugEnabled()) {
log.debug("Session validation completed successfully in " + (stopTime - startTime) + " milliseconds.");
}
}
就是validationSessions这个方法,处理了过期的session,并把它从那个hashMap中删除掉。实际调用了AbstractValidatingSessionManager.validateSessions这个方法。
逐步跟踪代码调用栈,当SimpleSession过期后,会抛出ExpiredSessionException异常,并被AbstractValidatingSessionManager.validate捕获到,并调用MemorySessionDao中的delete方法将session从map中删除。
MemorySessionDAO代码片段如下:
public void delete(Session session) {
if (session == null) {
throw new NullPointerException("session argument cannot be null.");
}
Serializable id = session.getId();
if (id != null) {
sessions.remove(id);
}
}
3.产生原因
不知道开发时候脑袋哪根筋掉线,居然初始化SessionManager的配置如下:
@Bean
public DefaultWebSessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(false);
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
把sessionValidationSchedulerEnabled设置成了false,这就意味着清除过期session的scheduler不能生效,也就无法清理map中的session,这个要是不内存溢出,天理难容!!!!!!!
4.问题修复
直接贴代码吧
@Bean
public DefaultWebSessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionIdUrlRewritingEnabled(false);
sessionManager.setSessionValidationInterval(DefaultWebSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL);
return sessionManager;
}
5.总结
修改代码10秒钟,问题调研10小时。