JVM 永久代会 GC 吗?99% 的 Java 程序员答错了!

138 阅读5分钟



前段时间,我一个朋友小雷跳槽去某大厂,面试官一开口就抛出了这个问题:

“你知道 JVM 中的永久代吗?它会不会发生 GC?”

小雷是个认真负责的老实人,愣了一下说:“我记得永久代是用来存储类的元数据的,应该不会 GC 吧?”

面试官嘴角一笑:“确定吗?”

这一问,小雷开始犹豫了……回来就跑来找我“支招”,我一听这题,笑了:“兄弟,这个题不简单,它不光考你是否了解 JVM,还考你是不是了解历史变迁!”

于是我决定,写下这篇文章,把我讲给小雷的,也讲给你听。

JVM的“江湖地图”:堆、非堆和永久代

在我们开始深入永久代 GC 的话题之前,先快速过一下 JVM 的内存结构(不然容易掉坑)。

JVM 的内存主要分成以下几块:

  • 方法区(Method Area) :保存类的结构、常量、静态变量等。
  • 堆(Heap) :保存对象实例,是 GC 主要管理的区域。
  • 虚拟机栈、程序计数器、本地方法栈:线程私有的,生命周期跟线程一致。

而所谓的 永久代(PermGen) ,就是 HotSpot 虚拟机对“方法区”的一种实现方式。换句话说:

永久代 ≈ 方法区的 HotSpot 版本

也就是说,它不是规范的一部分,而是 JVM 的“方言”。

永久代里藏了什么?

永久代是个“非堆”的内存区域,主要存放下面这些东西:

  • 类的结构信息(Class 对象)
  • 常量池
  • 类加载器
  • 方法信息
  • 字段信息
  • 注解(在 JDK 1.5+ 中)

简单来说,你加载一个类,它的结构就被放到永久代里了。所以你一旦有“类的动态加载”相关的逻辑,比如:

甚至是搞个热部署框架或自定义类加载器,那你就要担心永久代会不会炸。

重点来了:永久代会发生 GC 吗?

答案是:会的!

永久代中的内容是可以被垃圾回收的,只不过:

  • 不是每次 GC 都会回收永久代
  • 只有在特定情况下才触发
  • 不一定能成功回收所有内容

你可以把永久代 GC 看作是一种“高门槛、低频率”的垃圾回收。

JVM 中有两种 GC:

  • Minor GC:回收新生代
  • Full GC(或叫 Major GC) :回收老年代和永久代

也就是说,只有 Full GC 才可能回收永久代。但即便是 Full GC,也不一定会清理永久代,原因是:清理永久代是“按需进行”的。

如果类加载器还在用,或者 class 被引用着,那你就算手动触发 GC,它也不会回收。

一个常见事故:PermGen space OOM

小雷当初的公司在搞一个热部署系统,类频繁加载和卸载,结果过了一段时间,系统直接挂了,抛出这样的异常:

为什么会这样?因为虽然类被卸载了,但类加载器没有被回收,导致永久代空间一直被占着,最终爆炸!

永久代爆炸的典型场景:

  • 动态生成类(如 CGLIB、JSP)
  • 热部署/插件机制频繁加载类
  • Tomcat 重复部署应用(每次部署都会加载一批新类)

永久代 vs 元空间(MetaSpace)

在 JDK 8 之后,Oracle 官方终于“痛定思痛”,把永久代彻底干掉了,取而代之的是 元空间(MetaSpace)

区别如下:

元空间使用的是本地内存,不再受堆大小限制。这算是 Java 世界里的重大升级。但也带来了另一个问题:内存泄漏可能更隐蔽,因为不会轻易抛 OOM。

面试官到底想考你什么?

当面试官问“永久代会不会 GC”,其实是在考你:

  • 有没有深入了解 JVM 内存模型?
  • 知不知道永久代和方法区的区别?
  • 清不清楚不同 JDK 版本下的实现变迁?
  • 理不理解类加载器与类卸载的关系?

如果你只是简单答“不会”,那就是大大的误解。如果你能像我上面这么分析一遍,面试官多半就会点点头:“这小子,有点料。”

终极加分点:如何触发永久代 GC?

如果你想手动触发一次 Full GC,顺便看看永久代会不会被清理,可以这样做:

但这只是个“建议”,JVM 不一定理你。而且永久代要回收,类加载器必须是不可达的

你可以:

  • 自定义类加载器
  • 加载类、用完后置 null
  • 调用 System.gc()
  • 观察 GC 日志

GC 日志中有下面这些关键字就表示触发了永久代 GC:

[Full GC [PSYoungGen: ...]

[CMS Perm : ... ]

如果你看到 CMS Perm 或者 PermGen 的回收情况,那就是触发了。

日常开发中该注意什么?

虽然 JDK 8 之后永久代已经没了,但元空间同样也会被“填满”,你还是要小心类加载泄漏问题。

常见注意点:

  • 使用类加载器时,尽量释放引用
  • 动态生成类(如反射、CGLIB)后及时清理
  • 对插件式系统,建议做类卸载测试
  • 设置 -XX:MaxMetaspaceSize 控制元空间上限,避免吃光本地内存

写在最后:一题看穿 JVM 背后的“江湖”

这道题其实就像一根绣花针,戳中了 JVM 的一大痛点。理解它,不只是为了面试加分,更是为了让我们写出更稳定、更优雅的代码。

就像我常说的那句话:

面试的每一道题,都值得我们用工程师的眼光审视它背后的“底层逻辑”。

今天你学会了吗?

如果你喜欢这类 JVM 面试题解析,记得点个“在看”,顺手转发给还在苦学 GC 的小伙伴吧!

END

我是小米,一个每天都在 JVM 的世界里“打怪升级”的程序员,我们下次面试题故事再见啦!

我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!