方法区 / 元空间:JDK 1.7 到 JDK 1.8 到底变了什么?

方法区 / 元空间:JDK 1.7 到 JDK 1.8 到底变了什么? 如果你只想记一个结论JDK 1.7有永久代PermGenJDK 1.8移除永久代用元空间Metaspace走本地内存但只记结论不够因为你线上会遇到OutOfMemoryError: PermGen spaceOutOfMemoryError: Metaspace动态代理/反射/生成类太多导致内存异常这篇把“方法区是什么、放什么、为什么改、怎么排”讲透。1. 方法区是什么放“类本身的信息”的地方方法区是 JVM 规范里的一个概念主要存类元数据类结构、字段/方法信息、访问标志等运行时常量池字符串常量、符号引用等静态变量相关信息具体实现上会有差异JIT 相关的一些元数据不同实现有所不同一句话堆里放对象方法区元空间放类2. JDK 1.7永久代PermGen2.1 它为什么容易 OOM永久代属于 HotSpot 的实现细节不是 JVM 规范硬规定它的问题在于空间大小受 JVM 参数限制配置不当很容易顶满类卸载条件苛刻尤其在容器里反复热部署典型场景Tomcat 反复热部署类加载器泄露大量动态生成类CGLIB、ASM、Javassist运行时常量池/字符串占用膨胀2.2 常见参数JDK 1.7-XX:PermSize-XX:MaxPermSize3. JDK 1.8元空间Metaspace3.1 为什么要改把类元数据从永久代迁到元空间核心动机永久代太容易 OOM且难以调优类元数据更适合放到本地内存避免和 Java 堆争抢3.2 元空间还会 OOM 吗会。元空间用的是本地内存不等于无限如果你不设上限可能把机器内存吃光如果你设了上限达到上限一样会OutOfMemoryError: Metaspace3.3 常见参数JDK 1.8-XX:MetaspaceSize触发扩容/GC 的阈值不是硬上限-XX:MaxMetaspaceSize硬上限4. 运行时常量池很多人混淆的点你会听到“常量池在方法区里”但要注意几个历史变化规范层面运行时常量池属于方法区的一部分HotSpot 实现上JDK 版本间有多次迁移/调整工程上你记住就行类加载越多、常量越多方法区/元空间压力越大5. 实战怎么判断是 Metaspace 泄露还是堆泄露5.1 快速判断现象堆泄露Java heap space对象一直涨Full GC 也回不去元空间泄露Metaspace类数量/类元数据一直涨5.2 常用观测低侵入看类直方图jcmd pid GC.class_histogram看类加载统计jcmd pid VM.classloaders你重点盯两件事类数量是否持续增长是否存在“旧类加载器”长期存活热部署/插件系统常见6. 一个常见根因类加载器泄露“类卸载”发生的前提通常是对应的ClassLoader变成不可达如果你有静态集合缓存了某个类加载器加载的对象线程本地变量ThreadLocal引用了它第三方库注册监听器但不注销就会导致类加载器无法回收其加载的所有类元数据也无法卸载最终Metaspace OOM7. 总结方法区元空间放“类”堆放“对象”JDK 1.7的PermGen改成JDK 1.8的MetaspaceMetaspace也会 OOM关键看类是否不断增加、类加载器是否泄露排障优先从class_histogram和classloaders入手