zoukankan      html  css  js  c++  java
  • java8中字符串常量以及GC相应处理机制

    1,常量池

    1.1, class文件常量池

    ​ class文件常量池位于class文件中

    ​ class文件头4个字节称为魔数,魔数后面的4个字节为文件版本号,而版本号之后的就是常量池的入口。该常量池用于存放编译器生成的各种字面量和符号引用,字面量就是所谓的常量,如字符串,final修饰的常量值等。而符号引用则是用来描述引用目标的,如类和接口的全限定名,方法和字段的名称和描述符。此时符号引用并不会存储最终内存布局信息。

    ​ class文件中方法和字段均需要引用CONSTANT_Utf8型常量描述名称,该类型的最大长度也就是java中方法和字段名的长度,而该类型的长度用2个字节表示,最大值为65535,所以java中如果定义了超过64kb(2^16*1byte,utf8中一个字符占一个字节)的英文字符的变量或方法名,将无法编译。

    1.2, 运行时常量池

    ​ 当类或接口创建时,它的class中的常量池会被用来构造运行时常量池,常量池中的符号引用会被解析成具体的内存地址。运行时常量池是jvm方法区的一部分,它可以在运行时将符号引用解析为直接引用。

    ​ 运行时常量池位于jvm的元空间中(java8)

    1.3,字符串常量池

    ​ 字符串常量池底层实现是一个哈希表,可以通过-XX:StringTableSize参数调整大小。字符串常量池中存储的是字符串对象的引用,而字符串本身是在堆上分配的(java中的对象基本都在堆上分配)。运行时常量池初始化的时候,字面量的符号引用的初始化会用到字符串常量池。String中的intern方法可以在运行时将字符串实例加入字符串常量池。

    ​ 在java1.7以前,字符串常量池是在堆的永久代里面,大小固定,而从java1.7以后,字符串常量池则移动到java堆中了。

    验证代码如下:

    public class test {
        private static String str = "";
        public static void main(String[] args) {
            //-Xms32m -Xmx32m
            char element = 'a';
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < 1024*1024*64; i++) {
                sb.append(element);
            }
            str = sb.toString();
            System.out.println();
        }
    }
    

    JVM启动参数:-Xms32m -Xmx32m

    运行结果如下:

    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    	......
    	at test.test.main(test.java:17)
    

    可以发现堆发生溢出(oom),说明常量池在堆中分配。

    String.intern()

    String类的intern()方法的描述是:String类维护着一个初始化为空的字符串池,当某String实例调用intern()方法的时候,如果字符串池中已包含与此字符串相同的字符串(用equal(obj)方法判断相等),则返回池中的字符串对象。否则,将此字符串对象加入到常量池中,并返回此字符串对象的引用。对于任意两个字符串s和t,当切仅当s.equals(t)为true,s.intern() ==t.intern()才为true。所有字面值字符串和字符串赋值常量表达式都使用intern方法进行处理。

    示例:

    public class InternTest {
        public static void main(String[] args) {
            String s1 = new String("a");
            s1.intern();
            String s2 = "a";
            System.out.println(s1 == s2);
    
            String s3 = new String("b") + new String("b");
            s3.intern();
            String s4 = "bb";
            System.out.println(s3 == s4);
        }
    }
    

    运行结果如下:

    false
    true
    

    -XX:StringTableSize

    可以通过jvm参数-XX:StringTableSize来配置String常量池的大小,在java6和java7u40之前的版本,其默认大小为1009,java7u40以及之后的java8中其默认值为60013。其值要求得是素数。

    2,使用new关键字和使用字符串字面量的区别

    示例:

    String str1="abc";
    
    String str2=new String("abc");
    
    • 当使用String str="abc"这种形式的时候,JVM会在字符串常量池中创建该对象并且str1会指向这个对象;

    • 当使用String str=new String("abc")这种形式的时候,JVM会先检查在字符串常量池中是否已存在"abc",如果不存在则在常量池中创建该对象,如果已存在则不创建。除此之外,还会在堆外创建一个额外的字符串对象,并且str2会指向这个对象。

    验证代码:

    public class test {
        public static void main(String[] args) {
            String str1 = "abc";
            String str2 = new String("abc");
            System.out.println(str1);
            System.out.println(str2);
            System.out.println(str1 == str2);
        }
    }
    

    运行结果如下:

    abc
    abc
    false
    

    查看该代码的字节码,会发现两种创建字符串对象的方式的不同:

       L0
        LINENUMBER 9 L0
        LDC "abc" //从常量池加载String
        ASTORE 1
    
       L1
        LINENUMBER 10 L1
        NEW java/lang/String  //创建一个String对象
        DUP  
        LDC "abc"   //从常量池加载String
        INVOKESPECIAL java/lang/String.<init> (Ljava/lang/String;)V //调用构造方法,实例初始化
        ASTORE 2
    

    通过对比发现,使用new关键字与直接使用字符串字面量相比,多了创建对象的过程。

    3,字符串常量池与GC

    先看一个示例:

    public class test {
        public static void main(String[] args) {
            System.out.println("---------String with new--------");
            collectWeakReference(new String("dasfsafsafsafsa"));
            System.out.println("---------String with literal--------");
            collectWeakReference("dsafdsafxcdfeghg");
        }
        private static void  collectWeakReference(String obj ) {
            ReferenceQueue<String> rq = new ReferenceQueue<>();
            Reference<String> r = new WeakReference<>(obj, rq);
            obj = null;
            Reference rf;
            int gccount = 10;
            //一次System.gc()并不一定会回收A,所以要多试几次
            while((rf=rq.poll()) == null && gccount >=0) {
                System.gc();
                gccount--;
            }
            System.out.println(rf);
            if (rf != null) {//如果对象被回收则弱引用会加入引用队列
                //引用指向的对象已经被回收,存入引入队列的是弱引用本身,所以这里最终返回null
                System.out.println(rf.get());
            }
        }
    }
    

    运行结果如下:

    ---------String with new--------
    java.lang.ref.WeakReference@5a07e868
    null
    ---------String with literal--------
    null
    

    由此可见,指向new关键字创建的字符串对象的弱引用会被System.gc()触发的gc回收掉,而指向字面量字符串的弱引用则不会被System.gc()触发的gc回收掉。这说明new关键字创建的字符串对象如果不可达了会被gc回收,而字符串字面量创建的字符串对象则不会,因为常量池中还持有对该字面量字符串的引用。

    参考链接:

    https://netsurfingzone.com/core-java/string-constant-pool-in-java

    https://tangxman.github.io/2015/07/27/the-difference-of-java-string-pool/

    https://blog.csdn.net/zm13007310400/article/details/77534349

    https://stackoverflow.com/questions/23252767/string-pool-vs-constant-pool

    http://java-performance.info/string-intern-in-java-6-7-8/

    https://netsurfingzone.com/core-java/string-constant-pool-in-java

    https://www.cnblogs.com/gxyandwmm/p/9495923.html

    https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html

    https://blog.csdn.net/qq_26929957/article/details/79090406

    https://jimmy2angel.github.io/2018/10/26/ConstantPool/

    https://droidyue.com/blog/2014/12/21/string-literal-pool-in-java/

    https://blog.csdn.net/qq_31615049/article/details/81611918

    https://blog.omitol.com/2017/09/30/JVM-CP/

  • 相关阅读:
    jQuery 语法
    jQuery 简介
    把数据存储到 XML 文件
    XML 注意事项
    XML DOM (Document Object Model) 定义了访问和操作 XML 文档的标准方法。
    通过 PHP 生成 XML
    XML 命名空间(XML Namespaces)
    XML to HTML
    XMLHttpRequest 对象
    使用 XSLT 显示 XML
  • 原文地址:https://www.cnblogs.com/cord/p/11440303.html
Copyright © 2011-2022 走看看