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
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。