关于元空间是否会触发GC

写在文章开头

你好,我叫sharkchili,目前还是在一线奋斗的Java开发,经历过很多有意思的项目,也写过很多有意思的文章,是CSDN Java领域的博客专家,也是Java Guide的维护者之一,非常欢迎你关注我的公众号:写代码的SharkChili,这里面会有笔者精心挑选的并发、JVM、MySQL数据库专栏,也有笔者日常分享的硬核技术小文。

之前写过一篇是关于JVM方法区的文章时,引发读者的探讨,有读者认为元空间实际上是可能触发垃圾回收,遂笔者就以这篇文章来探讨一下这个问题。

深入理解Java虚拟机的说法

笔者查阅权威 《深入理解Java虚拟机》 中看到, 《Java虚拟机规范》 对于方法区的实现即元空间或者永久代垃圾回收行为没有强制要求。 原因很简单,方法区进行垃圾收集的回收的收益不是很大,它并不像堆内存的新生代那样,在一次新生代的垃圾回收就能回收 70%-90% 的内存空间。这也使得大部分人(包括笔者)认为方法区不涉及GC的,实际上对于 jdk8 版本的Hotspot 虚拟机而言,JVM 中某一个类符合以下这3个条件时将会卸载类并回收这个类的元数据空间:

  1. 在堆中没有任何基于当前类或者基于该类派生子类的实例。
  2. 该类的java.lang.Class对象没有在任何地方被引用,以及无法通过反射等方式访问该类的方法。
  3. 加载该类的类加载器被回收,这个条件除非是精心设计过的可替换类加载器的场景,否者很难实现。

需要注意的是,在判断是否有实例还在使用当前类以及是否有类加载器引用这个类这两个步骤的时候,为了能够明确这两点,可能需要扫描全部堆空间的,这也就意味着元空间的回收可能伴随着FullGC。

注意事项

可以看到最后一点比较苛刻,所以就导致如果我们使用Spring 等框架通过增强技术生成大量的新类型载入元空间内存,导致元空间内存溢出 (Caused by: java.lang.OutOfMemoryError: Metaspace) ,就像下面这段代码一样,为了更快看到效果,我们手动设置一下元空间大小-XX:MetaspaceSize=100m -XX:MaxMetaspaceSize=100m:

scss 复制代码
public static void main(String[] args) {
       while (true){
           Enhancer enhancer = new Enhancer();
           //设置代理目标
           enhancer.setSuperclass(EmptyObject.class);

           enhancer.setUseCache(false);

           //设置单一回调对象,在调用中拦截对目标方法的调用
           enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invokeSuper(objects, args));

           enhancer.create();
       }

    }

我们通过jconsole定位查看当前进程的类加载信息:

可以看到大量EmptyObject的增强类被加载至元空间中:

键入命令jmap 定位加载的类信息再次进行确认:

yaml 复制代码
jmap -histo 4532

可以看到生成了大量的net.sf.cglib.proxy相关的类

vbnet 复制代码
 num     #instances         #bytes  class name
----------------------------------------------
   1:       3824742      600680704  [C
   2:       1932145      170028760  java.lang.reflect.Method
   3:       3806008       91344192  java.lang.String
   4:       1779516       37754664  [Ljava.lang.Class;
   5:         26568       15064520  [I
   6:        618402       14841648  net.sf.cglib.core.Signature
   7:         79344       12595728  java.lang.Class
   8:        154765       12381200  java.lang.reflect.Constructor
   9:        308844        9883008  net.sf.cglib.proxy.MethodProxy
  10:        308844        9883008  net.sf.cglib.proxy.MethodProxy$CreateInfo

我们以MethodProxy 进行定位可以看到这个类是在create 方法创建的,这也就意味着上述代码的最后一个create 方法会创建大量的MethodProxy并存到元空间中导致元空间内存溢出:

ini 复制代码
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
        MethodProxy proxy = new MethodProxy();
        proxy.sig1 = new Signature(name1, desc);
        proxy.sig2 = new Signature(name2, desc);
        proxy.createInfo = new CreateInfo(c1, c2);
        return proxy;
    }

所以尽管说jdk8 将类信息存到原空间中,但我们日常进行开发也需要留意对于cglib等增强技术的使用是否得当,如果发现大量的增强类出现在元空间时,需要及时定位并解决。

小结

我是sharkchiliCSDN Java 领域博客专家开源项目---JavaGuide contributor ,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili ,同时我的公众号也有我精心整理的并发编程JVMMySQL数据库个人专栏导航。

参考

《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) 》

为啥Metadata GC会触发Full GC? - Hashcon的回答 - 知乎: www.zhihu.com/question/44...

本文使用 markdown.com.cn 排版

相关推荐
代码之光_19801 小时前
保障性住房管理:SpringBoot技术优势分析
java·spring boot·后端
ajsbxi1 小时前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
颜淡慕潇2 小时前
【K8S问题系列 |1 】Kubernetes 中 NodePort 类型的 Service 无法访问【已解决】
后端·云原生·容器·kubernetes·问题解决
尘浮生3 小时前
Java项目实战II基于Spring Boot的光影视频平台(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·maven·intellij-idea
尚学教辅学习资料3 小时前
基于SpringBoot的医药管理系统+LW示例参考
java·spring boot·后端·java毕业设计·医药管理
monkey_meng4 小时前
【Rust中的迭代器】
开发语言·后端·rust
余衫马4 小时前
Rust-Trait 特征编程
开发语言·后端·rust
monkey_meng4 小时前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
paopaokaka_luck8 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
码农小旋风10 小时前
详解K8S--声明式API
后端