关于java程序OOM的优化

在很多时候,我们使用循环,在循环体中处理逻辑使用的了大量内存,最终导致
程序OOM。对此,经过一些测试,最终找出优化方案。

策略

  1. 将不使用的对象赋值为null
  2. 主动调用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---");
    }
}

测试结果

  1. 未做处理的方案:

    程序启动后,观察打印结果,发现每次循环所需时间略小于1s,内存占用稳定在14.6GB
  2. 对象赋值为null:

    程序启动后,观察打印结果,发现每次循环所需时间略小于2s,内存占用稳定在14.5GB
  3. 对象赋值为null,并在循环体最后调用GC:

    程序启动后,观察打印结果,发现每次循环所需时间大约1s,内存占用稳定在13.5GB
  4. 对象不使用时立即赋null,并调用GC:

    程序启动后,观察打印结果,发现每次循环所需时间大约1s,内存占用稳定在12.6GB

总结

经过以上4种方案的结果对比,发现第4种方案对象不使用时立即赋null,并调用GC是最适合
处理这类问题的。但是不建议在未出现OOM或者内存占用飙升之前这样处理,因为会显得你很拽。
治未病虽然更牛,但是治已病更能彰显自己的作用。并且值得注意的是,不要将对象赋值null和
调用gc一起封装成一个方法,因为java是值传递而不是引用传递。

posted @ 2022-12-09 13:33  小小爬虫  阅读(92)  评论(0编辑  收藏  举报