题记:昨天加班打车回家,看见前面有辆路虎在高速上开的巨慢,挡住了我坐的出租车的路,于是就跟司机吐槽了一句:“前面这车怎么这么面啊?”,司机沉默了大概3秒,说了一句富含哲理性的话:“没有面车,只有面人”。借用这句话套在软件开发上就是:“没有面代码,只有面的程序猿”。只不过这次我就是那个面的程序猿。(面:是一个方言,大意就是优柔寡断,反应迟缓,没有主见,好欺负之类的)。

背景是这样的,最近项目要做国际化,主要是基于spring的i18来做,通过拦截器拦截request过来的url中是否包含locale参数,如果locale参数指定了语言类型,则页面上显示的信息按指定的类型来获取,如content这个字段,如果locale=zh_CN,则content=“你好”,如果locale=en_US,则content=“hello”;返回页面的时候,同样根据locale,在返回页面的路径前加上具体目录,如返回sayHi.html,locale=zh_CN时返回cn/sayHi.html,locale=en_US时返回us/sayHi.html,这样可以显示不同内容的欢迎页面,一般国际化也是这样做的,同一套代码,不同配置就可以完成国际化。

于是开始的时候我的拦截器代码时这样写的:

public class LocaleHandleInterceptor extends LocaleChangeInterceptor {

    @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException {
String newLocale = request.getParameter(this.getParamName());
if (newLocale != null) {
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
if (localeResolver == null) {
throw new IllegalStateException("No LocaleResolver found,may be not config localeResolver bean!");
}
Locale locale = StringUtils.parseLocaleString(newLocale);
localeResolver.setLocale(request, response, locale);
request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, locale);
} else {
Object localObject = request.getSession().getAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME);
if (null == localObject) {
Locale locale = RequestContextUtils.getLocale(request);
locale = (null == locale) ? Locale.getDefault() : locale;
request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, locale);
}
}
// Proceed in any case.
return true;
} @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if (null != modelAndView) {
// Locale locale =
// (Locale)request.getSession().getAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME);
Locale locale = (Locale) WebUtils.getSessionAttribute(request, SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME);
modelAndView.setViewName(locale.toString() + "/" + modelAndView.getViewName());
}
} }

写完后跑了几个例子,换了一个locale参数设置都没有问题,这件事情就算完成了。

过了一段时间后进行上线前的压力测试,用loadrunner 500用户并发压测tomcat(jvm Xmx=1024M)的时候发现曲线比较奇怪,刚开始表现正常,但大约30秒后tps急剧下降,一分钟后tps趋于0,但是tomcat并没有报OOM内存溢出,用浏览器访问某个url还是能有返回,用gcutil查看内存回收情况,发现每秒都在做full gc,于是用jmap把当前tomcat的堆栈打出来,看到很多concurrentHashMap对象,对象里有很多locale对象,联想到这块代码最近只加过国际化的拦截器,于是怀疑到是不是session导致的,一开始没有改代码,而是在tomcat的conf下找到web.xml,调整了session-timeout 为1分钟,重新压测问题还是复现,重新审视代码,发现是这段代码引起的问题,把它注释掉就正常:

// Object localObject = request.getSession().getAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME);

找到问题后,对request.getSession()进行分析,此方法内部调用的是request.getSession(true),即没有session的时候会创建,压力测试下大并发访问导致过多的session对象创建用于存储locale,在一定mina gc后都移到jvm old区,引起频繁的full gc,拖慢整个响应。那为什么没有报OOM呢?我猜测是响应慢后拖慢了压测机打过来的请求,full gc每次都能清理出一些空间出来,避免了OOM。不知道是不是这样?基于这种session的方式本身是可以做国际化的,只是我调用的方式不对,于是参考spring的LocaleChangeInterceptor和SessionLocaleResolver类的源码将我们的拦截器代码修改如下:

public class LocaleHandleInterceptor extends LocaleChangeInterceptor {

    @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws ServletException {
String localName = request.getParameter(this.getParamName());
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
if (localeResolver == null) {
throw new IllegalStateException("No LocaleResolver found: not in a DispatcherServlet request?");
}
Locale locale = localeResolver.resolveLocale(request);
if(!(null ==localName || locale.toString().equalsIgnoreCase(localName))){
localeResolver.setLocale(request, response, (null == localName?Locale.getDefault():StringUtils.parseLocaleString(localName)));
}
// Proceed in any case.
return true;
} @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//Locale locale = (Locale)request.getSession().getAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME);
if(null != modelAndView){
// 过滤json返回的情况
Locale locale = RequestContextUtils.getLocaleResolver(request).resolveLocale(request);
modelAndView.setViewName(locale.toString()+"/"+modelAndView.getViewName());
}
} }

之后再压测就没问题了。

后记:不经过压测的代码不足以谈优秀,出了任何问题,先抛开之前的先入为主根深蒂固的观念,从源头分析,大胆猜测,小心求证,并保持学习的心态,绕开问题很容易,但是直面问题需要勇气和耐心,踏过了它,你就会前进一步,而技术上的前进是技术人员真正的财富。

利用session做国际化引起的old区内存爆满及修复方法的更多相关文章

  1. Tomcat 6.x Perm区内存泄露问题

    Tomcat 6.x JSP文件最后改动时间大于当前系统时间导致Perm区内存泄露问题(java Memory pool CMS Perm Gen) 出现场景: 因为測试业务,须要模拟跨天測试,所以一 ...

  2. 基于Session的国际化实现

    如何将我们网站的其它内容(如菜单.标题等)做国际化处理呢?这就是本篇要将的内容—>国际化. 在项目的spring.xml文件添加的内容如下 <mvc:interceptors> &l ...

  3. AJAX安全-Session做Token

    个人思路,请大神看到了指点 个人理解token是防止扫号机或者恶意注册.恶意发表灌水,有些JS写的token算法,也会被抓出来被利用,个人感觉还是用会过期的Session做token更好,服务器存储, ...

  4. 利用Session实现三天免登陆

    什么是Session Session:在计算机中,尤其是在网络应用中,称为“会话控制”.(百度百科) Session:服务器端的数据存储技术. Session要解决什么问题 一个用户的不同请求(重定位 ...

  5. Java基础-继承 利用接口做参数,写个计算器,能完成+-*/运算

    38.利用接口做参数,写个计算器,能完成+-*/运算 (1)定义一个接口Compute含有一个方法int computer(int n,int m); (2)设计四个类分别实现此接口,完成+-*/运算 ...

  6. 【转】asp.net中利用session对象传递、共享数据[session用法]

    来自:http://blog.unvs.cn/archives/session-transfer-method.html 下面介绍Asp.net中利用session对象传递.共享数据用法: 1.传递值 ...

  7. 利用SCI做的一个足球答题系统

    SCI,异步串行通信接口,内置独立的波特率产生电路和SCI收发器,可以选择发送8或9个数据位(其中一位可以指定为奇或偶校验位). SCI是全双工异步串行通信接口,主要用于MCU与其他计算机或设备之间的 ...

  8. asp.net中利用session对象传递、共享数据[session用法]

    下面介绍Asp.net中利用session对象传递.共享数据用法: 1.传递值: 首先定义将一个文本值或单独一个值赋予session,如下: session[“name”]=textbox1.text ...

  9. 利用toString做类型的判断

    //利用toString做类型的判断 : /*var arr = []; alert( Object.prototype.toString.call(arr) == '[object Array]' ...

随机推荐

  1. Oracle ORA-01033: ORACLE initialization or shutdown in progress 错误解决办法

    Oracle ORA-01033: ORACLE initialization or shutdown in progress 错误解决办法 登陆数据库时提示 “ORA-01033”错误在命令窗口以s ...

  2. 恶心的hadoop集群

    具体配置:网上一堆,我说一下我的问题好了! “完全” 分布式集群 注意地方有三点: 1.你的"master" dfs目录中的某个Id不一致,具体位置,有空我再找找.(经过我找了一下 ...

  3. 分享一个点赞超过100的漂亮ASP.NET MVC蓝色界面框架

    从 陈贞宝 博客中看到一个MVC模板感觉特别漂亮就尝试着分离出来,直接拿来用啦,直接拷贝到自己的常用的代码库里收藏起来,地址是http://www.cnblogs.com/baihmpgy/p/381 ...

  4. LCLFramework框架之Repository模式

    Respository模式在示例中的实际目的小结一下 Repository模式是架构模式,在设计架构时,才有参考价值: Repository模式主要是封装数据查询和存储逻辑: Repository模式 ...

  5. java单例之enum实现方式

    传统的两私有一公开(私有构造方法.私有静态实例(懒实例化/直接实例化).公开的静态获取方法)涉及线程安全问题(即使有多重检查锁也可以通过反射破坏单例), 目前最为安全的实现单例的方法是通过内部静态en ...

  6. Centos 6.5 下安装socket5代理

    ---恢复内容开始--- 1. 安装 先安装依赖库 yum -y install gcc gcc-c++ automake make pam-devel openldap-devel cyrus-sa ...

  7. Digg Reader 登录不了,原来如此

    cdnjs.cloudflare.com 不能访问,你懂的,给 https://cdnjs.cloudflare.com 跟 http://cdnjs.cloudflare.com 架个梯子就可以了, ...

  8. C8051 SMBus 原理

    一.SMBus总线   SMBus串行I/O接口完全符合系统管理总线规范 1.1 版.它是一个双线的双向串行总线,与I2C串行总线兼容.系统控制器对总线的读写操作都是以字节为单位的,由SMBus接口自 ...

  9. Java帮助文档的生成

    首先需要对代码加上文档的注释,比如下面这样: package wz.learning;        /**   * Title:Person<br>   * Description:  ...

  10. UFT\QTP 12 新特性

    UFT\QTP 12 新特性 http://blog.csdn.net/testing_is_believing/article/details/22310297