关于java:什么JVM-老年代内存不断上涨竟是因为获取-ServletContext-姿势不对

47次阅读

共计 2482 个字符,预计需要花费 7 分钟才能阅读完成。

前几日始终在筹备一个比拟大的我的项目,发现一个问题,还好流量不是十分十分大,不然又得提桶跑路了。在线上运行的时候发现当并发过高的状况,会呈现老年代内存上涨的状况。

定位问题

我找了个台机器 dump 了堆内存了,交给了我组攻坚小队长 LAY,应用相似于 MAT 一样的内存剖析工具,发现内存泄露的最大可能性是 ConcurrentHashMap,进一步在 摆布树 中发现,ConcurrentHashMap 存的都是org.apache.catalina.session.StandardSession,根本定位是 tomcat 的 session 机制导致的。

然而咱们我的项目是不依赖 tomcat 的 session 机制的,所有的会话放弃都通过 ticket 去和账号核心交互,在咱们零碎不存储。怎么还是会用到 tomcat 的默认 session 对象呢?十万个问号在头脑里浮现。

查看 org.apache.catalina.session.ManagerBase 代码,能够看到 session 是一个 ConcurrentHashMap

protected Map<String, Session> sessions = new<ConcurrentHashMap>();

能够用 arthas 看下线上的状况

watch org.apache.catalina.session.ManagerBase findSession '{params,returnObj,throwExp,target.sessions.size()}'  -n 1  -x 3

通过排查代码发现,咱们我的项目中,在一个 filter 外面写了一段如下代码

ServletContext sc = httpServletRequest.getSession().getServletContext()

搜代码是一个简略计划,然而不是 100% 有用,如果搜不到代码,最好是通过 arthas stack 查看调用生成 session 的调用栈

好家伙,为什么获取 ServletContext 要从 session 绕一圈呢?间接就能拿啊 httpServletRequest.getServletContext()

Demo 验证

而后通过一个小试验能够发现当代码里不显性的调用 session 则不会存储 session 的。

@RestController
@Slf4j
public class ApiController {

    @Autowired
    HttpServletRequest httpServletRequest;

    @RequestMapping("/hello")
    public String hello() {ServletContext sc1 = httpServletRequest.getServletContext();
        ServletContext sc2 = httpServletRequest.getSession().getServletContext();

        if (!sc1.equals(sc2)) {return "500";}

        return "200";
    }

}
$ curl -i http://127.0.0.1:8080/hello
HTTP/1.1 200 
Set-Cookie: JSESSIONID=7893E89199F79E2EC1BA0EB25D1DCD47; Path=/; HttpOnly
Content-Type: text/plain;charset=UTF-8
Content-Length: 3
Date: Mon, 25 Oct 2021 05:48:24 GMT

200

能够看到想获取 ServletContext 其实不须要通过 session 去取,两种形式获取到的是同一个对象。所以 httpServletRequest.getSession().getServletContext() 形式大可不必。

@RestController
@Slf4j
public class ApiController {

    @Autowired
    HttpServletRequest httpServletRequest;

    @RequestMapping("/hello")
    public String hello() {ServletContext sc1 = httpServletRequest.getServletContext();

        return "200";
    }

}
$ curl -i http://127.0.0.1:8080/hello
HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 3
Date: Mon, 25 Oct 2021 05:49:09 GMT

200%

当初返回的 header 外面就没有了 JSESSIONID 了。

解决方案

尽管 tomcat 自身是有过期 session 清理计划的,ContainerBackgroundProcessor 线程专门做这件事,能够通过配置

server.tomcat.background-processor-delay = 10

来启动,默认是不开启的。不过并发过高的状况,也于事无补,毕竟默认是 30 分钟的过期工夫。并且咱们当初的利用都不依赖 tomcat 默认的 session 所以不要调用 getSession 的办法是最好的。

为什么是老年代

年老代其实分为两局部,别离是 1 个 Eden 区和 2 个 Survivor 区 (别离叫 from 和 to),默认比例是8:1:1,个别状况下,新创建的对象都会被调配到 Eden 区,(除非一些特地大的对象会间接放到老年代),当 Eden 没有足够的空间的时候,就会触发 jvm 发动一次 Minor GC,如果对象通过一次 Minor GC 还存活,并且又能被 Survivor 空间承受,那么将被挪动到 Survivor 空间当中,对象在 Survivor 区中每熬过一次 Minor GC,年龄就会减少一岁,当它的年龄减少到肯定水平(15 岁)时,就会被移到老年代中,当然降职老年代的年龄能够通过-XX:MaxTenuringThreshold 来设置。

https://zhuanlan.zhihu.com/p/…

正文完
 0