字符串常量池中的字符串对象可被GC回收,前提是无任何强引用指向它,且JDK7+后池位于堆中,由Full GC或G1/ZGC并发标记阶段清理。

字符串常量池中的对象**可以被垃圾回收**,但前提是它们**不再被任何地方引用**,且满足 JVM 的 GC 条件。关键在于:常量池本身不是 GC 的“禁区”,它只是方法区(JDK 7+ 在堆中)的一块特殊存储区域,其中的字符串对象和其他对象一样,受 Java 垃圾回收器统一管理。
常量池对象何时会被回收?
字符串常量池中的字符串对象(即 String 实例,不是字面量本身)能否被回收,取决于该对象是否还存在可达的强引用:
- 如果某个字符串字面量(如
"hello")仅存在于常量池中,且没有任何变量、静态字段、局部变量等指向它(即没有强引用),那么它就符合 GC 条件; - JDK 7 及以后,字符串常量池被移到了Java 堆中,因此其对象和普通堆对象一样,可被 CMS、G1 等收集器正常回收;
- JDK 6 中常量池在永久代(PermGen),而永久代在 Full GC 时才可能被清理,且回收条件更苛刻(如类卸载触发),所以那时字符串常量池对象极难被回收。
哪些操作会增加/减少对常量池字符串的引用?
理解引用关系是判断能否回收的核心:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
字面量直接赋值(
String s = "abc";)会让s持有对常量池中"abc"对象的强引用; -
调用
String.intern():若池中已有该字符串,则返回已有引用;若没有,则将当前字符串对象(或其副本)放入池并返回引用——这会新增一个强引用到池中对象; - 局部变量作用域结束、对象被置为 null、容器被清空等,都可能切断强引用,使对象进入可回收状态;
- 类卸载(少见):如果某个类加载器加载的类中定义了私有静态字符串字面量,且该类加载器被回收,相关常量池项才可能被清理(JDK 7+ 中需同时满足堆中无引用)。
如何验证常量池字符串是否被回收?
可通过组合手段间接观察(注意:不能直接“看到”回收动作,但能推断结果):
立即学习“Java免费学习笔记(深入)”;
- 使用
-XX:+PrintGCDetails -Xlog:gc*:file=gc.log(JDK 9+)或-XX:+PrintStringDeduplicationStatistics(针对 G1 字符串去重)观察 GC 日志; - 配合
Runtime.getRuntime().totalMemory()和freeMemory()在大量intern()后主动触发 GC,看内存是否回落; - 用 JFR(Java Flight Recorder)或 VisualVM 的堆直方图查看
java.lang.String实例数变化; - 注意:
String::new创建的对象不在常量池,new String("abc").intern()才可能影响池中引用关系。
常见误区澄清
几个容易混淆的点需要明确:
- “字符串常量池里的对象永远不会被回收”是错误认知——这是 JDK 6 时代遗留误解,现代 JVM 已不成立;
-
字面量本身不是对象,它是编译期符号引用;运行时真正被 GC 的是常量池中对应的
String实例; -
调用
intern()不一定增加新对象:若池中已存在内容相同的字符串,就复用原有实例,不会创建新对象也不会改变引用计数; - final static String 是强引用,只要类没卸载、字段没被覆盖,它就阻止对应字符串被回收。

















