前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JVM元数据区

JVM元数据区

作者头像
shysh95
发布2020-07-21 11:32:56
1.5K0
发布2020-07-21 11:32:56
举报
文章被收录于专栏:shysh95
之前我们已经讲述过Java栈和堆,今天我们看一下JVM另一块重要的内存区域,那就是元数据区。读者如果对JVM的其他文章感兴趣的话可以通过上方专辑进入查看JVM系列文章
元数据区

元数据区的概念出现在Java8以后,在Java8以前成为方法区,元数据区也是一块线程共享的内存区域,主要用来保存被虚拟机加载的类信息、常量、静态变量以及即时编译器编译后的代码等数据。

由于元数据存储的信息不容易变动,因此它被安置在一块堆外内存,大小由-XX:MaxMetaspaceSize指定。

代码语言:javascript
复制
public class MetaSpaceTest {

    public static void main(String[] args) {
        int i = 0;
        try {
            for (i = 0; i < 100000; i++) {
                new CglibBean(new HashMap<>());
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            System.out.println("total create count:" + i);
        }
    }

    public static class CglibBean {

        public CglibBean(Object object) {
            Enhancer enhancer = new Enhancer();
            enhancer.setUseCache(false);
            enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> obj);
            enhancer.setSuperclass(object.getClass());
            enhancer.create();
        }
    }
}

上述代码通过Cglib生成大量的HashMap代理,下面我们在运行这段代码的时候指定下列参数

代码语言:javascript
复制
-XX:MaxMetaspaceSize=100M -XX:+PrintGCDetails

当我们程序循环至3660次,也就是说我们大约在生成了约3660个代理类以后元数据区发生了内存溢出,下面将MaxMetaspaceSize改为50M执行,

从上图可以看出当我们生成了1710个代理类以后元数据区发生了内存溢出,可见一个元数据区的大小决定了Java虚拟机可以装载的类的多少。

运行时常量池

在元数据区中还有一块区域称为运行时常量池,此区域用来程序运行期间产生的常量,以及编译期生成的各种字面量和符号引用经类加载后的内容。

在Java中大概存储三种常量池概念,下面我们来讲一下Java中其他两种常量池,帮助读者了解他们中的区别。

首先大家在理解常量池的时候不要简单的理解为被final修饰的变量,常量在这里的含义是一切不变的东西,包括final修饰的变量、字面量、类和接口全限定名、字段、方法名称以及修饰符等永恒不变的东西

类文件常量池

类文件常量池是指.class文件中的Constant_Pool项,如下图,类文件常量池存放的都是一些字面量和符号引用。

并不是所有的字面量都会存储在类文件常量池中,比如对于方法内(注意是方法)整数字面量,如果值在-32768~32767之间则会被直接嵌入JVM指令中去,不会保存在常量池中。所以读者不会在常量池中知道CONSTANT_Integer_info为1的符号引用。

类文件常量池产生于编译时期,当JVM加载类文件时会将类文件常量池中的符合引用替换直接引用,加载之后的类文件信息将会被存放在运行时常量池。

字符串池

字符串池存在JDK1.6以前是存放在永久区中,但是在JDK1.7以后就被转移到堆上。

代码语言:javascript
复制
public static void intern() {
    String s1 = new String("he") + new String("llo");
    String s2 = s1.intern();
    System.out.println(s1 == s2);
}

上述代码在JDK1.6的时候将会创建6个对象,首先new String("he")会在堆上创一个对象,并且"he"字面量会在永久区的字符串池上创建一个对象,new String("llo")同理创建了两个对象,最后的+又创建了一个对象,当调用intern()方法时,首先会去查找字符串池查找是否有hello内容的对象,发现没有则会在永久区中再创建一个对象,因此总共有6个对象,由于s1是堆中的对象,s2是永久区字符串池中的对象,因此s1==s2结果为false,详情如下图

但是在JDK1.6以后效果不再如此,原因就是由于字符串常量池被移到了堆中,intern方法也做了优化,在JDK1.6以后上述代码将会创建5个对象,首先new String("he")会在堆上创一个对象,并且"he"字面量也会在堆上创建一个对象, new String("llo")同理创建了两个对象,最后的+又创建了一个对象。当intern调用时,首先会在字符串池中查找是否有hello内容的对象,发现没有,此时不会主动创建而是先去查找堆中是否有hello内容的对象,如果有则直接将指针指向堆中的示例,因此这里一共会创建5个对象,由于s1和s2指向的是同一个对象实例,因此s1==s2为true,详情如下图

问题

为了帮助各位读者真正理解字符串常量池,下面有两段代码,请在脑海中给出结果,然后再进行Coding验证

代码语言:javascript
复制
public static void intern() {
    String s1 = new String("he") + new String("llo");
    String s2 = s1.intern();
    String s3 = "hello";
    System.out.println(s1 == s3);
    System.out.println(s1 == s2);
}
代码语言:javascript
复制
public static void intern() {
    String s3 = "hello";
    String s1 = new String("he") + new String("llo");
    String s2 = s1.intern();
    System.out.println(s1 == s3);
    System.out.println(s1 == s2);
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员修炼笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 之前我们已经讲述过Java栈和堆,今天我们看一下JVM另一块重要的内存区域,那就是元数据区。读者如果对JVM的其他文章感兴趣的话可以通过上方专辑进入查看JVM系列文章
  • 元数据区
    • 运行时常量池
      • 类文件常量池
      • 字符串池
  • 问题
相关产品与服务
数据保险箱
数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档