关于java程序OOM的优化
在很多时候,我们使用循环,在循环体中处理逻辑使用的了大量内存,最终导致
程序OOM。对此,经过一些测试,最终找出优化方案。
策略
- 将不使用的对象赋值为null
- 主动调用GC
测试
前言
- 程序逻辑为循环100次,每次生成一个大内存的StringBuilder对象,
- 将StringBuilder对象转成String对象,再将String对象转成byte[],
- 最后打印byte[]长度。
- pc机内存为16GB,未启动程序前通过任务管理器观察到内存占用11GB
测试代码
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CompletableFuture;
@Slf4j
public class TestGc {
//未做处理
private static void test1() {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 30000000; i++) {
stringBuilder.append(i);
}
String str = stringBuilder.toString();
byte[] bytes = str.getBytes();
log.info("current-size:{} byte",bytes.length);
}
//对象赋值为null
private static void test2() {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 30000000; i++) {
stringBuilder.append(i);
}
String str = stringBuilder.toString();
byte[] bytes = str.getBytes();
log.info("current-size:{} byte",bytes.length);
bytes = null;
str = null;
stringBuilder = null;
}
//对象赋值为null,并在最后调用gc
private static void test3() {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 30000000; i++) {
stringBuilder.append(i);
}
String str = stringBuilder.toString();
byte[] bytes = str.getBytes();
log.info("current-size:{} byte",bytes.length);
bytes = null;
str = null;
stringBuilder = null;
System.gc();
}
//对象不使用时立即赋null,并调用gc
private static void test4() {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 30000000; i++) {
stringBuilder.append(i);
}
String str = stringBuilder.toString();
stringBuilder = null;
System.gc();
byte[] bytes = str.getBytes();
str = null;
System.gc();
log.info("current-size:{} byte",bytes.length);
bytes = null;
System.gc();
}
public static void main(String[] args) {
CompletableFuture future = CompletableFuture.runAsync(()->{
try {
for (int x = 0; x < 100; x++) {
test1();
//test2();
//test3();
//test4();
}
}catch (Exception e) {
e.printStackTrace();
}
});
log.info("--start---");
future.join();
log.info("--end---");
}
}
测试结果
- 未做处理的方案:
程序启动后,观察打印结果,发现每次循环所需时间略小于1s,内存占用稳定在14.6GB
- 对象赋值为null:
程序启动后,观察打印结果,发现每次循环所需时间略小于2s,内存占用稳定在14.5GB
- 对象赋值为null,并在循环体最后调用GC:
程序启动后,观察打印结果,发现每次循环所需时间大约1s,内存占用稳定在13.5GB
- 对象不使用时立即赋null,并调用GC:
程序启动后,观察打印结果,发现每次循环所需时间大约1s,内存占用稳定在12.6GB
总结
经过以上4种方案的结果对比,发现第4种方案
对象不使用时立即赋null,并调用GC
是最适合
处理这类问题的。但是不建议在未出现OOM或者内存占用飙升之前这样处理,因为会显得你很拽。
治未病虽然更牛,但是治已病更能彰显自己的作用。并且值得注意的是,不要将对象赋值null和
调用gc一起封装成一个方法,因为java是值传递而不是引用传递。
不积跬步无以至千里