当前位置: 首页 > 后端技术 > Java

什么?JVM老年代内存一直在上升的原因是获取ServletContext的姿势不对

时间:2023-04-02 00:15:01 Java

前几天一直在准备一个比较大的项目,发现了一个问题。在线运行时发现并发过高时老年代的内存会增加。为了定位问题,我找了一台dumpheapmemory的机器,交给了我组长LAY。使用类似MAT的内存分析工具,发现最有可能发生内存泄漏的是ConcurrentHashMap,并进一步在支配树中发现,ConcurrentHashMap存储了所有org.apache.catalina.session.StandardSession,基本定位是tomcat的session机制造成的。但是我们的项目并没有依赖tomcat的session机制。所有会话都通过票据与帐户中心进行交互,并且不会存储在我们的系统中。为什么还用tomcat默认的session对象?脑海里出现了十万个问号。查看org.apache.catalina.session.ManagerBase代码,可以看到session是一个ConcurrentHashMap:protectedMapsessions=new();你可以使用arthas来观察离线情况watchorg.apache。catalina.session.ManagerBasefindSession'{params,returnObj,throwExp,target.sessions.size()}'-n1-x3通过查看代码发现,在我们的项目中,在一个filterServletContext中写了如下代码sc=httpServletRequest.getSession().getServletContext()搜索代码是一个简单的解决方案,但并不是100%有用。如果找不到代码,最好通过arthasstack查看调用生成session的调用栈。好家伙,为什么要从session中获取ServletContext呢?一个圆怎么样?可以直接httpServletRequest.getServletContext()获取。Demo验证然后通过一个小实验可以发现,当代码中没有显式调用session时,session是不会被存储的。@RestController@Slf4j公共类ApiController{@AutowiredHttpServletRequesthttpServletRequest;@RequestMapping("/hello")publicStringhello(){ServletContextsc1=httpServletRequest.getServletContext();ServletContextsc2=httpServletRequest.getSession().getServlet()!sc1.equals(sc2)){返回"500";}返回“200”;}}$curl-ihttp://127.0.0.1:8080/helloHTTP/1.1200Set-Cookie:JSESSIONID=7893E89199F79E2EC1BA0EB25D1DCD47;路径=/;HttpOnlyContent-Type:text/plain;charset=UTF-8Content-Length:3Date:Mon,25Oct202105:48:24GMT200可以看到不需要通过session获取ServletContext,可以获取它在两个方面是同一个对象。所以httpServletRequest.getSession().getServletContext()是不必要的。@RestController@Slf4j公共类ApiController{@AutowiredHttpServletRequesthttpServletRequest;@RequestMapping("/hello")publicStringhello(){ServletContextsc1=httpServletRequest.getServletContext();返回“200”;}}$curl-ihttp://127.0.0.1:8080/helloHTTP/1.1200Content-Type:text/plain;charset=UTF-8Content-Length:3Date:Mon,25Oct202105:49:09GMT200%现在返回的标头中没有JSESSIONID。解决方案虽然tomcat本身有一个过期会话清理的解决方案,但是ContainerBackgroundProcessor线程专门用于此。可以通过配置server.tomcat.background-processor-delay=10来启动,默认是不开启的。但是,如果并发度太高,就无济于事了。毕竟默认的过期时间是30分钟。而我们现在的应用并不依赖tomcat默认的session,所以最好不要调用getSession。为什么老年代和新生代其实分为两部分,一个Eden区和两个Survivor区(分别叫from和to),默认比例是8:1:1,一般情况下,新创建的对象会被Allocated到Eden区(除非一些特别大的对象会直接放在老年代),当Eden空间不够时,会触发jvm发起一次MinorGC,如果对象在一次MinorGC中存活下来,就可以Accepted通过Survivor空间,它将被移动到Survivor空间。对象在Survivor空间中每经历一次MinorGC,其年龄就会增加一年。当它的年龄增长到一定程度(15岁)时,它就会被移除。移动到老年代,当然提升到老年代的年龄可以通过-XX:MaxTenuringThreshold来设置。https://zhuanlan.zhihu.com/p/…