You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java SoftReference在OOM前未回收?两种实现差异解惑

问题背景

测试代码1及输出

public class Test {

    public static SoftReference<byte[]> cache = new SoftReference<>(new byte[0]);

    public static List<byte[]> list = new ArrayList<>();

    public static void main(String[] args) {
        try {
            func();
        } catch (OutOfMemoryError e) {
            sniff();
            e.printStackTrace();
        }
    }

    public static void func() {
        byte[] bytes = new byte[1024 * 1024];
        cache = new SoftReference<>(bytes);
        for(;;) {
            byte[] tmp = new byte[1024 * 1024];
            list.add(tmp);
        }
    }

    public static void sniff() {
        byte[] bytes = cache.get();
        if (bytes == null) {
            System.out.println("recycling data.");
        } else {
            System.out.println("object still live");
        }
    }
}

输出:

object still live

测试代码2及输出

public class Test {

    public static SoftReference<byte[]> cache = new SoftReference<>(new byte[0]);

    public static List<byte[]> list = new ArrayList<>();

    public static void main(String[] args) {
        try {
            func();
        } catch (OutOfMemoryError e) {
            sniff();
            e.printStackTrace();
        }
    }

    public static void func() {
        for(;;) {
            byte[] tmp = new byte[1024 * 1024];
            list.add(tmp);
            byte[] bytes = new byte[1024 * 1024];
            cache = new SoftReference<>(bytes);
        }
    }

    public static void sniff() {
        byte[] bytes = cache.get();
        if (bytes == null) {
            System.out.println("recycling data.");
        } else {
            System.out.println("object still live");
        }
    }
}

输出:

recycling data.

根据Oracle官方文档描述:

All soft references to softly-reachable objects are guaranteed to have been cleared before the virtual machine throws an OutOfMemoryError.

用户疑问:

  1. 第一种写法中,垃圾回收器为何未回收SoftReference指向的对象?
  2. 为何两种写法会产生如此差异?

问题解答

1. 第一种写法未回收的原因

核心是软引用指向的byte[]对象仍处于强可达状态:
在第一种写法的func方法中,byte[] bytes是方法级局部变量,当代码进入无限for循环后,func方法并未执行完毕,这个bytes变量始终存在于当前栈帧的局部变量表中——也就是说,该byte[]对象有一个强引用(bytes变量)还在生效。

软引用的回收规则是:仅当对象是软可达(无任何强引用指向,仅被软引用关联)时,GC才会在OOM发生前优先回收这类对象。由于第一种情况里对象是强可达的,GC没有回收它的理由,所以cache.get()能拿到对象,输出object still live。

2. 两种写法的差异根源

第二种写法把byte[] bytes = new byte[1024 * 1024]; cache = new SoftReference<>(bytes);放进了for循环体内部:

  • 每次循环结束后,当前迭代的bytes局部变量会随循环体栈帧销毁而失效,对应的强引用消失;
  • 新循环迭代会创建新的bytes变量和byte[]对象,同时更新cache指向新对象——此时旧的byte[]对象仅剩下软引用关联,成为软可达状态;
  • 内存耗尽触发OOM前,GC会清理所有软可达对象,最后cache指向的最后一次循环创建的对象,也因循环结束后强引用消失被回收,所以cache.get()返回null,输出recycling data.。

内容的提问来源于stack exchange,提问作者Colin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 03:17:08