- 先说结论
- 排查过程
- char[]共占用900M内存,其中99%是被String引用的
- 而String占用内存368M+900M,大部分是被SimpleSession和SysFunction/SysRole引用的
- 随便找几个SysFunction看一下引用关系
- 跟踪GC Root
- 从下图可以看出,都是ExecutorServiceSessionValidationScheduler创建的
- 看一下CustomerShiroSessionDao.getActiveSessions(),难道我的redis有很多Session?
- 找到SessionKey的前缀
- 登录redis-cli,查询keys shiro_session:*
- 来看一下Session里面有哪些内容
- 改进建议
- 遗留问题
先说结论
Session采用redis管理之后,定时清理过期Session的逻辑需要优化。
CustomerShiroSessionDao.getActiveSessions()这个方法,
用于AbstractValidatingSessionManager.validateSessions()调用,
用于ExecutorServiceSessionValidationScheduler定时清理过期的Session
目前的实现逻辑是从redis中读取所有sessionKey,再遍历所有key一个个读取Session
Session数据量较大时,就会出现内存溢出。
排查过程
上周五遇到内存溢出,把内存调大,当时没出现了,今天又出现了,看来是个问题
java.lang.OutOfMemoryError: Java heap space
将内存dump到文件中,用JProfiler分析,发现大量的SysFunction对象,65万个实例,这个肯定不正常
char[]共占用900M内存,其中99%是被String引用的
而String占用内存368M+900M,大部分是被SimpleSession和SysFunction/SysRole引用的
随便找几个SysFunction看一下引用关系
跟踪GC Root
从下图可以看出,都是ExecutorServiceSessionValidationScheduler创建的
这个类是用于定时清理过期的Session
看一下CustomerShiroSessionDao.getActiveSessions(),难道我的redis有很多Session?
找到SessionKey的前缀
登录redis-cli,查询keys shiro_session:*
是挺多的。
但按说1712个Session也不应该溢出,实际上只加载了1390个Session就已经溢出了
来看一下Session里面有哪些内容
有一个currFunctions的List
SysFunction对象下面还有children和buttons两个List
Session中放了太多东西了,看来这就是内存溢出的原因了。
改进建议
采用Redis管理session之后,有些逻辑需要优化,我在之前的项目中解决过
毕竟从本地JVM变成socket请求了,不是建一个CustomerShiroSessionDao就行的,需要全盘考虑
1、优化从Redis清理过期Session的逻辑,利用Redis的过期机制
创建Session时设定30分钟过期,用户访问时更新Session的过期时间(1分钟访问多次也只刷新一次)
2、利用线程变量缓存Session信息,同一个WEB请求只会从Redis读取一次Session信息
否则一个WEB请求会发几十次从Redis读取Session,Redis也会成为瓶颈
3、List
SysFunction用Map结构保存到Redis中,整个系统一共只有100多个(目前的存储方式是Session数量x100多)
Session中只保存当前用户对应的List
要用到的时候再根据List
4、不要直接缓存SysFunction这个DTO对象,建一个新对象,只放菜单名称,图标等必要信息
去掉createUser/createTime/updateUser/updateTime等用不到的字段
遗留问题
为什么会产生这么多Session呢?我的redis只是我自己在用,我只登录过一个账号。。。