2

I have been preparing for a relatively large project a few days ago and found a problem. Fortunately, the traffic is not very, very large, otherwise I will have to carry buckets and run away. When running online, it is found that when the concurrency is too high, the memory of the old generation will rise.

Positioning problem

I found a machine to dump the heap memory and gave it to my team leader LAY . Using MAT , I found that the maximum possibility of memory leaks is ConcurrentHashMap , which was further found dominance tree. ConcurrentHashMap stored in org.apache.catalina.session.StandardSession , the basic positioning is caused by the session mechanism of tomcat.

But our project does not rely on tomcat's session mechanism. All sessions are maintained through tickets to interact with the account center and are not stored in our system. Why is the default session object of tomcat still used? One hundred thousand question marks appeared in the mind.

Looking org.apache.catalina.session.ManagerBase code of 0619d9cb2e13f9, you can see that session is a ConcurrentHashMap :

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

You can use arthas to see the offline situation

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

image.png

Through the investigation of the code, we found that in our project, we wrote a piece of code as follows in a filter

ServletContext sc = httpServletRequest.getSession().getServletContext()
Search code is a simple solution, but not 100% helpful, if not search the code, preferably by arthas stack view the call stack calls generated session of

Good guy, why do you need to ServletContext around the session to get 0619d9cb2e14bf? You can get httpServletRequest.getServletContext() directly.

Demo verification

Then through a small experiment, it can be found that when session is not explicitly called in the code, the session will not be stored.

@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

It can be seen that if you want to get ServletContext , you don't actually need session . The two methods get the same object. Therefore, the httpServletRequest.getSession().getServletContext() method is unnecessary.

@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%

Now the returned header does not JSESSIONID .

solution

Although tomcat itself has an expired session cleaning program, the ContainerBackgroundProcessor thread specifically does this, which can be configured

server.tomcat.background-processor-delay = 10

To start, it is not turned on by default. However, the high concurrency situation does not help, after all, the default expiration time is 30 minutes. And our current applications do not rely on the default session of tomcat, so the best way is not to call getSession.

Why the old age

The young generation is actually divided into two parts, one Eden area and two Survivor areas (called from and to respectively). The default ratio is 8:1:1 . In general, newly created objects will be allocated to the Eden area (unless some Especially large objects will be directly placed in the old generation). When Eden does not have enough space, it will trigger jvm to initiate a Minor GC. If the object survives a Minor GC and can be accepted by Survivor space, it will be Move to the Survivor space. Every time the object passes through the Minor GC in the Survivor area, its age will increase by one year. When its age increases to a certain level (15 years old), it will be moved to the old age, and of course it will be promoted. The age of the old generation can be set -XX:MaxTenuringThreshold

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

周梦康
9k 声望6.7k 粉丝

退隐江湖