用户请求过来, 后端查询数据库后封装Vo对象返回给前端后,然后正常这个Vo就可以被GC清理掉了。
但并发时,如果数据处理时间很长,大量对象存于内存,或者处理用户请求后,没有及时删除用户数据对象,就会导致无用对象在堆内存堆积,进而OOM。关键点:
示意Demo:
@RestController
public class LeakController {
private static Map<Long, Students> userCache = new HashMap<>();
/**
* 大量数据 + 处理慢
*/
@GetMapping("/leak1")
public String doSome() throws InterruptedException {
byte[] bytes = new byte[1024 * 1024 * 100]; //100M
//本来这个bytes对象会释放,但由于后面的代码,这个对象在内存中愣是多待了十秒钟
Thread.sleep(1000 * 10L);
return "success";
}
/**
* 没有及时清除用户数据对象
*/
@PostMapping("/leak2")
public String doSome2(Long id, String name) {
//用户不再用这个对象以后,它还在静态变量map中存着
userCache.put(id, new Students(id, name));
return "test";
}
}
@Setter
@Getter
@NoArgsConstructor
public class Students {
private Long id;
private String name;
private byte[] info = new byte[1024 * 1024 * 100]; //模拟大对象
public Students(Long id, String name) {
this.id = id;
this.name = name;
}
/**
* 这里实体类的equals和hashcode都正常,没有前一篇笔记说的那种内存泄漏的错误写法
* 只比id属性即可,ID相同,则同一个对象
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Students students = (Students) o;
return Objects.equals(id, students.id);
}
/**
* id属性
*/
@Override
public int hashCode() {
return Objects.hash(id);
}
}
调整下堆内存大小,方便后面验证。
-Xms5g -Xmx5g
压测上面的接口,并发下OOM,则系统在用户高峰期一定会崩。
100线程数并发,循环执行两次:
接口传参这里用Jmeter的随机函数:
复制生成的表达式做为传参值:
HTTP Request如下,添加结果数和摘要报告组件后启动:
结果:
控制台OOM: