调用intern()后堆中String对象的回收时机及内存优化问题
让我们逐个拆解你的问题,把String.intern()背后的内存逻辑说清楚:
问题1:调用intern()方法后,内存中新创建的String对象何时会被清理?
首先得明确两个核心逻辑:
sb.toString()会在堆内存中生成一个全新的String对象;intern()的作用是:检查字符串常量池,如果池里已经有相同内容的字符串引用,就返回这个池里的引用;如果没有,就把当前堆中这个String对象的引用加入常量池,再返回它。
那这个新创建的堆String对象的回收时机,完全取决于它是否还有可达的强引用:
比如你的代码里,string变量先指向sb.toString()生成的对象,随后被赋值为intern()返回的常量池引用——这时候原来的堆对象就彻底失去了所有强引用。只要JVM触发垃圾回收(比如新生代内存不足时的Young GC,或者全局的Full GC),并且这个对象被标记为不可达,就会被GC清理掉。
问题2:示例中调用string.intern()后,sb.toString()创建的1000个String对象何时会被回收?
结合你的代码场景来看,每次循环里的对象生命周期非常短:
sb是方法内的局部变量,每次循环结束后就会出栈,失去对它的引用;string被重新赋值为intern()返回的常量池引用,所以每次循环生成的sb.toString()对象,在当前循环迭代结束后就没有任何强引用了。
这些对象属于新生代的临时对象,只要JVM触发Young GC(这在日常业务中是很频繁的),它们就会被快速回收。哪怕你跑了1000次循环,这些临时对象也不会长期占用内存——除非你的JVM内存极度充足,完全没有内存压力,GC一直没被触发,但这种情况在实际业务中很少见。
另外补充个细节:你的代码里sb是空的,所以sb.toString()生成的是空字符串"",而JVM的字符串常量池默认就存在""的引用。所以每次intern()都会直接返回池里的"",这意味着每个循环里的堆空字符串对象,从被创建开始就只有string这一个临时引用,赋值后就彻底无引用了,回收条件非常宽松。
补充问题:若GC未运行,使用intern()是否无意义?有没有减少内存占用/对象创建的方法?
1. GC未运行时,intern()依然有价值
哪怕GC没触发,堆里的临时对象还在,但intern()的核心价值是复用字符串常量池中的对象——后续如果再有相同内容的字符串创建需求(比如其他地方也生成空字符串),就不会再创建新的堆对象了,直接复用常量池里的引用。长期来看,这能从根源上减少重复字符串的对象数量,降低内存占用,而不是只靠GC事后回收临时对象。
2. 使用intern()时优化内存的实用方法
- 优先用字符串字面量:如果字符串内容是固定的,直接写
String s = "xxx",而不是用new String("xxx")或者StringBuilder拼接后再intern()——字面量会直接在常量池中创建,避免多余的堆对象。 - 对高频重复字符串提前intern():如果某些字符串会被频繁生成(比如业务状态码、固定枚举值),在第一次生成后就调用
intern(),后续所有地方都复用这个常量池引用,彻底避免重复创建堆对象。 - 自定义弱引用缓存(可选):如果担心
intern()会让常量池积累太多长期无用的对象(毕竟常量池里的引用是强引用,不会被GC回收),可以用WeakHashMap实现一个自定义字符串缓存——它会自动回收没有其他外部引用的缓存对象,既实现复用,又避免内存泄漏。示例代码:private static final WeakHashMap<String, String> STRING_CACHE = new WeakHashMap<>(); public static String internCustom(String s) { synchronized (STRING_CACHE) { String cached = STRING_CACHE.get(s); if (cached != null) { return cached; } STRING_CACHE.put(s, s); return s; } } - 避免对唯一字符串调用intern():如果每个字符串都是独一无二的(比如UUID、随机生成的字符串),调用
intern()完全没有意义,只会让常量池里积累大量无用引用,反而浪费内存。
内容的提问来源于stack exchange,提问作者Gokul Raj Kumar

