A series of articles on this topic:
Each Java program can only use a certain amount of memory, and this limitation is determined by the startup parameters of the JVM. The more complicated situation is that the memory of Java programs is divided into two parts: Heap space and Permanent Generation (Permgen):
The maximum memory size of these two areas is specified by the JVM startup parameters -Xmx and -XX:MaxPermSize. If not explicitly specified, it is determined according to the platform type (OS version + JVM version) and the size of the physical memory. If when creating a new object, the space in the heap memory is not enough to store the newly created object, it will cause java.lang.OutOfMemoryError: Java heap space error. No matter there is no free physical memory on the machine, as long as the heap memory usage reaches the maximum memory limit, a java.lang.OutOfMemoryError: Java heap space error will be thrown.Cause Analysis:
The reason for the java.lang.OutOfMemoryError: Java heap space error is often similar to stuffing the object of the XXL number into the Java heap space of the S number. In fact, if you know the reason, it's easy to solve, right? Just increase the size of the heap memory, and the program can run normally. In addition, there are some more complicated situations, mainly caused by code problems:
The amount of visits/data exceeded expectations. When designing an application system, there is generally a definition of "capacity", so many machines are deployed to process a certain amount of data/business. If the number of visits soars suddenly and exceeds the expected threshold, which is similar to a pinpoint-shaped map in the time coordinate system, then the program is likely to be stuck during the time period when the peak is located, and java.lang.OutOfMemoryError: Java heap space error is triggered. .
Memory leak. This is also a common situation. Due to some errors in the code, the system occupies more and more memory. If there is a memory leak in a certain method/a certain piece of code, every time it is executed, it will take up more memory (there are more garbage objects) . As the running time goes by, the leaked object consumes all the memory in the heap, then the java.lang.OutOfMemoryError: Java heap space error broke out.
A very simple example:
The following code is very simple. The program tries to allocate an int array with a capacity of 2M. If the startup parameter -Xmx12m is specified, then java.lang.OutOfMemoryError: Java heap space error will occur. As long as the parameter is slightly modified to -Xmx13m, the error will no longer occur.
publicclass OOM {static final int SIZE=2*1024*1024; publicstaticvoidmain(String[] a) {int[] i = newint[SIZE]; }}
public class OOM {
static final int SIZE=2*1024*1024;
public static void main(String[] a) {
int[] i = new int[SIZE];
}
}
Memory leak example:
This example is more realistic. In Java, when creating a new object, such as Integer num = new Integer(5);, there is no need to manually allocate memory. Because the JVM automatically encapsulates and handles memory allocation. During the execution of the program, the JVM will check if necessary which objects are still in use in the memory, and those objects that are no longer used will be discarded and the memory occupied by them will be discarded. Recycle and reuse. This process is called garbage collection. The module responsible for garbage collection in the JVM is called the garbage collector (GC).
Java's automatic memory management relies on GC. GC scans the memory area over and over again and deletes unused objects. Simply put, memory leaks in Java are those logically no longer used objects, but they are not garbage collected The program is killed. As a result, garbage objects continue to occupy the heap memory, gradually accumulate, and finally cause java.lang.OutOfMemoryError: Java heap space error.
It is easy to write a BUG program to simulate a memory leak:
import java.util.*;
public class KeylessEntry {
static class Key {
Integer id;
Key(Integer id) {
this.id = id;
}
@Override
public int hashCode() {
return id.hashCode();
}
}
public static void main(String[] args) {
Map m = new HashMap();
while (true){
for (int i = 0; i < 10000; i++){
if (!m.containsKey(new Key(i))){
m.put(new Key(i), "Number:" + i);
}
}
System.out.println("m.size()=" + m.size());
}
}
}
At a glance, you may think that there is no problem, because this caches up to 10,000 elements! But a closer examination will find that the Key class only rewrites the hashCode() method, but does not rewrite the equals() method, so it will continue Add more keys to the HashMap.
As time goes by, there will be more and more "cached" objects. When the leaked objects occupy all the heap memory and the GC can't clean it up, a java.lang.OutOfMemoryError: Java heap space error will be thrown.
The solution is simple, implement the equals() method appropriately in the Key class:
@Override
public boolean equals(Object o) {
boolean response = false;
if (o instanceof Key) {
response = (((Key)o).id).equals(this.id);
}
return response;
}
To be honest, when looking for the real cause of a memory leak, you may die many, many brain cells.
A scene in SpringMVC
In order to be easily compatible with the code migrated from Struts2 to SpringMVC, the request is directly obtained in the Controller.
So in the ControllerBase class, the request object held by the current thread is cached through ThreadLocal:
public abstract class ControllerBase {
private static ThreadLocal<HttpServletRequest> requestThreadLocal = new ThreadLocal<HttpServletRequest>();
public static HttpServletRequest getRequest(){
return requestThreadLocal.get();
}
public static void setRequest(HttpServletRequest request){
if(null == request){
requestThreadLocal.remove();
return;
}
requestThreadLocal.set(request);
}
}
Then in the SpringMVC Interceptor implementation class, in the preHandle method, save the request object to ThreadLocal:
public class LoginCheckInterceptor implements HandlerInterceptor {
private List<String> excludeList = new ArrayList<String>();
public void setExcludeList(List<String> excludeList) {
this.excludeList = excludeList;
}
private boolean validURI(HttpServletRequest request){
String uri = request.getRequestURI();
Iterator<String> iterator = excludeList.iterator();
while (iterator.hasNext()) {
String exURI = iterator.next();
if(null != exURI && uri.contains(exURI)){
return true;
}
}
LoginUser user = ControllerBase.getLoginUser(request);
if(null != user){
return true;
}
return false;
}
private void initRequestThreadLocal(HttpServletRequest request){
ControllerBase.setRequest(request);
request.setAttribute("basePath", ControllerBase.basePathLessSlash(request));
}
private void removeRequestThreadLocal(){
ControllerBase.setRequest(null);
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
initRequestThreadLocal(request);
if (false == validURI(request)) {
throw new NeedLoginException();
}
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
removeRequestThreadLocal();
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
removeRequestThreadLocal();
}
}
In the postHandle and afterCompletion methods, clean up the request object in ThreadLocal.
But in actual use, business developers set a large object (such as a List occupying about 200MB of memory) as the Attributes of the request and pass it to the JSP.
If an exception may occur in the JSP code, SpringMVC's postHandle and afterCompletion methods will not be executed.
Thread scheduling in Tomcat may not be able to schedule the thread that throws the exception, so ThreadLocal always holds the request. As the running time goes by, the available memory is filled up, and the Full GC is being executed all the time, and the system is directly stuck.
Subsequent amendments: Through Filter, clean up ThreadLocal in the finally block.
@WebFilter(value="/*", asyncSupported=true)
public class ClearRequestCacheFilter implements Filter{
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
clearControllerBaseThreadLocal();
try {
chain.doFilter(request, response);
} finally {
clearControllerBaseThreadLocal();
}
}
private void clearControllerBaseThreadLocal() {
ControllerBase.setRequest(null);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
ThreadLocal can be used, but there must be a controlled release measure, usually a try-finally code form.
Explanation: In the Controller of SpringMVC, the request can be injected through @Autowired. The actual injection is an HttpServletRequestWrapper object, and the current request is also called through the ThreadLocal mechanism during execution.
Conventional way: Just receive the request parameter directly in the controller method.
0 comments:
Post a Comment