• 今天我们基于jdk8聊聊JVM-常量池,希望对大家有帮助!


    前言

    本文主要讲 三种常量池, 类文件中常量池,运行时常量池,字符串常量池  各自存放的地方,以及对代码的影响

    类文件中常量池(The Constant Pool)

    经过javac编译后的class文件 记录了这个类的所有信息,其中一个部分被成为常量池,里面存放编译器生成的 字面量(Literal)和符号引用(Symbolic References)

    字面量:

    1.文本字符串

    2.8中基本类型的值

    3.final 常量等

    符号引用:

    1.类和接口的全限定名

    2.字段的名称和描述

    3.方法的名称和描述

    运行时常量池 (The Run-Time Constant Pool)

    常量池是在 class文件中的,当该类被加载,常量池信息就会放入运行时常亮池,并把里面的符号引用改为真是地址,运行时常量池是在方法区(1.8之后跟随方法区一起移动到元空间), 可以在JVM运行期间动态向常量池写入数据

    运行时常量的包装类

    8种基本数据类型都有自己的包装类,在包装类对象创建的实话就会消耗资源,因此  java 对 其中5种(Byte,Short,Integer,Long,Character,Boolean)包装类实现了常量池技术,默认创建了数值(-128 ,127)的相应类型的缓存数据,但是超出了此范围依然会去创建新的对象。两种浮点数类型的包装类 (Float,Double) 并没有实现常量池技术

    我们知道,Integer是int的包装类,而包装类是对象,创建对象就需要消耗资源.
    java中的基本类型的包装类基本都实现了常量池技术.

    代码演示:

            //只拿Integer 举例
            Integer a = 10; //此处有一步装箱操作 Integer  valueOf
            Integer b = 10;
            System.out.println(a == b);//实现了常量池技术,所以此处相等
            Integer c = 200;
            Integer d = 200;
            System.out.println(c == d);
     
            Integer e = new Integer(10);
            System.out.println(a == e);// new 了新对象  与a 所在的常量池 地址不一样
     
            Integer f = new Integer(10);
            f = f+0; // 关键在于此处进行了运算,固 f 在此处进行了自动拆箱操作,拆箱后 是int 数值
            System.out.println(a == f);
    

    看下valueOf 源码,默认大小范围内的值 被放入到了IntegerCache中,否则new 新对象

       public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
        }
    

    字符串常量池

    JVM为了提升性能和减少内存开销,避免字符串的重复创建,其维护了一块特殊的内存空间,储存不重复的字符串.,字符串常量存在于堆中

    创建方式

    1.在直接使用双引号"" 声明字符串的时候,java 都会去常量池通过equal找有没有相同的字符串,如果有,则将常量池的引用返回给变量,如果没有 回在常量池中创建一个对象,然后返回这个对象的引用

    2.使用new 关键字创建,例如 String a = new String("a"),这里首先会去常量池对比有没有"a",没有则会创建,其次 new 一定会在堆里面创建一个新对象 并返回该对象的引用

    3.使用+ 运算符,此处有大致有三种情况,

    String str = "ab"+"cd"; 在常量池上创建常量ab, cd ,abcd 返回 abcd【着重了解abcd在常量池上】

    String str = new String("ab") + new String("cd");  在堆上创建对象ab、cd和 abcd,在常量池上创建常量ab和cd ,常量池上不会创建 abcd【着重了解abcd 不会在常量池上】

    还有混合使用的就不在一一说明 如 String str = "ab" + new String("cd"); 或者 String strAb = "ab"; String str = strAb + new String("cd") 等情况

    现在需要了解一个非常关键的方法 public native String intern();

    简单来说就是intern用来返回常量池中的某字符串,如果常量池中已经存在该字符串,则直接返回常量池中该对象的引用。否则,在常量池中加入该对象【JDK1.7之后不再是把该字符串直接加入常量池,而是将其地址引用放到常量池】,然后 返回引用

    以下代码演示(特别说明 自己写demo 时候 无关代码一定要移除,哪怕一个简单 String str="ab"; 哪怕不引用也会影响代码的预期结果,并且全部在jdk8 下调试)

    代码1:验证 字符串与new

           String s1 = "ab"; //s1指向 ab常量池地址
           String s2 = new String("ab"); //s2 指向 堆 里面ab 的地址
           System.out.println(s1==s2);// 固为false
    

    代码2:验证intern()方法

            String s1 = "ab"; //s1指向 ab常量池地址
            String s2 = new String("ab"); //s2 指向 堆 里面ab 的地址
            String s3 = s2.intern();// s3 指向 "ab" 所在常量池地址
            System.out.println(s1 == s2);// false
            System.out.println(s1 == s3);// true ,由于s3指向 ab所在常量池 与s1 指向一直  固为true
            System.out.println(s2 == s3);// 固为false
    

    代码3:验证运算符

            String s2 = new String("a") +"b"; //s2 指向 堆 里面ab 的地址,并且常量池不存在 ab
            String s3 = s2.intern();// 由于常量池并没有ab 因此会把 s2 的堆地址引用 放到常量池
            System.out.println(s2 == s3);// 同时指向 ab 堆里面的地址 固为true
    

    代码4:验证运算符与字符串混用,和代码2的区别仅仅就是多定义了一个String s1 ="ab",返回结果就截然不同

            String s1 = "ab"; //s1指向 ab常量池地址
            String s2 = new String("a") +"b"; //s2 指向 堆 里面ab 的地址,常量池已存在 ab
            String s3 = s2.intern();// 由于常量池存在 ab 因为会把 ab 常亮池的引用返回
            System.out.println(s2 == s3);// s2 指向堆 s3地址字符常量池 固为false
    

    代码5:来一个特殊字符串 与代码3 一样仅仅是字符串替换成了"java" 返回结果为false  其实此处就是因为jvm虚拟机在其他类【Version.class】先定义了放入了常量池,其实原理就和代码4一样 先把ab 放入了常量池 了解了原理就可以举一反三

            String s2 = new String("ja") +"va"; //s2 指向 堆 里面java 的地址,常量池已存在 java【原因查看Version.class】
            String s3 = s2.intern();// 由于常量池存在 java 因为会把 java 常亮池的引用返回
            System.out.println(s2 == s3);// s2 指向堆 s3地址字符常量池 固为false
    

    附上Version.class截图

    总结

    在文章的最后作者为大家整理了很多资料!包括java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书等等!
    如果有需要的朋友欢迎关注公众号:前程有光,领取!

  • 相关阅读:
    CEF解决加载慢问题
    CEF之CefSettings设置locale
    Win32程序支持命令行参数的做法(转载)
    VC++实现程序重启的方法(转载)
    CEF之CefSettings设置日志等级
    libcurl开源库在Win32程序中使用下载文件显示进度条实例
    libcurl开源库在Win7 + VS2012环境下编译、配置详解 以及下载文件并显示下载进度 demo(转载)
    使用ShellExecute打开目标文件所在文件夹并选中目标文件
    linux下gimp的使用
    linux下的chm阅读器?
  • 原文地址:https://www.cnblogs.com/lwh1019/p/14603679.html
Copyright © 2020-2023  润新知