jvm深度学习(5):常量池与String

18 篇文章 3 订阅

        本文分静态常量池运行时常量池字符串常量池三个方面去分析。

        先提一句,对于字符串常量池官方从未给过任何关于其的定义,不知从哪来,也不知哪去,有各种对其的争议,有人说它属于运行时常量池,也有人说他是和运行时常量池并行的一个常量池。本文对其单独拿出来讲解,并不对争议进行站队,没有必要,更没有意义。

 

 

静态常量池(又称Class常量池)

        在 class 文件中除了有类的版本、字段、方法和接口等描述信息外,还有一项信息是常量池 (Constant Pool Table),用于存放编译期间生成的各种字面 量和符号引用。

字面量:给基本类型变量赋值的方式就叫做字面量或者字面值。
比如:String a=“b” ,这里“b”就是字符串字面量,同样类推还有整数字面值、浮点类型字面量、字符字面量。

符号引用 :符号引用以一组符号来描述所引用的目标。符号引用可以是任何形式的字面量,JAVA 在编译的时候一个每个 java 类都会被编译成一个 class 文件,但在编译的时候虚拟机并不知道所引用类的地址(实际地址),就用符号引用来代替,而在类的解析阶段(后续 JVM 类加载会具体讲到)就是为了把 这个符号引用转化成为真正的地址的阶段。
        一个 java 类(假设为 People 类)被编译成一个 class 文件时,如果 People 类引用了 Tool 类,但是在编译时 People 类并不知道引用类的实际内存地址,因 此只能使用符号引用(org.simple.Tool)来代替。而在类装载器装载 People 类时,此时可以通过虚拟机获取 Tool 类的实际内存地址,因此便可以既将符号 org.simple.Tool 替换为 Tool 类的实际内存地址。

 

 

运行时常量池

       运行时常量池(Runtime Constant Pool)是每一个类或接口的常量池(Constant_Pool)的运行时表示形式,它包括了若干种不同的常量: 从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。(这个是虚拟机规范中的描述,很生涩)
       运行时常量池是在类加载完成之后,将 Class 常量池中的符号引用值转存到运行时常量池中,类在解析之后,将符号引用替换成直接引用。
       运行时常量池在 JDK1.7 版本之后,就移到堆内存中了,这里指的是物理空间,而逻辑上还是属于方法区(方法区是逻辑分区)。
       在 JDK1.8 中,使用元空间代替永久代来实现方法区,但是方法区并没有改变,所谓"Your father will always be your father"。变动的只是方法区中内容的物理存放位置,但是运行时常量池和字符串常量池被移动到了堆中。但是不论它们物理上如何存放,逻辑上还是属于方法区的。

 

 

字符串常量池

        字符串常量池这个概念是最有争议的,虚拟机规范等很多正式文档,没有这个概念的官方定义,所以与运行时常量池的关系不去抬杠,我们从它的作用和 JVM 设计它用于解决什么问题的点来分析它。
       以 JDK1.8 为例,字符串常量池是存放在堆中,并且与 java.lang.String 类有很大关系。设计这块内存区域的原因在于:String 对象作为 Java 语言中重要的数据类型,是内存中占据空间最大的一个对象。高效地使用字符串,可以提升系统的整体性能。 所以要彻底弄懂,我们的重心其实在于深入理解 String。

 

String

String 类分析(JDK1.8)
        String 对象是对 char 数组进行了封装实现的对象,主要有 2 个成员变量:char 数组,hash 值。

String 对象的不可变性
        了解了 String 对象的实现后,你有没有发现在实现代码中 String 类被 final 关键字修饰了,而且变量 char 数组也被 final 修饰了。
        我们知道类被 final 修饰代表该类不可继承,而 char[]被 final+private 修饰,代表了 String 对象不可被更改。Java 实现的这个特性叫作 String 对象的不可变性,即 String 对象一旦创建成功,就不能再对它进行改变。


Java 这样做的好处在哪里呢?

第一, 保证 String 对象的安全性。假设 String 对象是可变的,那么 String 对象将可能被恶意修改。
第二, 保证 hash 属性值不会频繁变更,确保了唯一性,使得类似 HashMap 容器才能实现相应的 key-value 缓存功能。
第三, 可以实现字符串常量池。在 Java 中,通常有两种创建字符串对象的方式,一种是通过字符串常量的方式创建,如 String str=“abc”;另一种是字符串变量通过 new 形式的创建,如 String str = new String(“abc”)。

String 的创建方式及内存分配的方式

1、String str=“abc”;
当代码中使用这种方式创建字符串对象时,JVM 首先会检查该对象是否在字符串常量池中,如果在,就返回该对象引用,否则新的字符串将在常量池中 被创建。这种方式可以减少同一个值的字符串对象的重复创建,节约内存。(str 只是一个引用)

2、String str = new String(“abc”)
        首先在编译类文件时,"abc"常量字符串将会放入到常量结构中,在类加载时,“abc"将会在常量池中创建;其次,在调用 new 时,JVM 命令将会调用 String 的构造函数,同时引用常量池中的"abc” 字符串,在堆内存中创建一个 String 对象;最后,str 将引用 String 对象。

3、使用 new,对象会创建在堆中,同时赋值的话,会在常量池中创建一个字符串对象,赋值到堆中。
具体的赋值过程是先将常量池中的字符串压入栈中,在使用 String 的构造方法是会拿到栈中的字符串作为构方法的参数。 这个构造函数是一个 char 数组的赋值过程,而不是 new 出来的,所以是引用了常量池中的字符串对象。存在引用关系。

    public static void main(String[] args) throws Exception{
        Location location = new Location();
        location.setProvice("安徽");
        location.setCity("合肥");
        location.setRiver("店埠河");
    }

 

 

总结:在堆中的成员变量的值是通过字符串构造方法赋值过去的。

4、String str2= "ab"+ "cd"+ "ef";

        编程过程中,字符串的拼接很常见。前面我讲过 String 对象是不可变的,如果我们使用 String 对象相加,拼接我们想要的字符串,是不是就会产生多个对象呢?例如代码:String str2= "ab"+ "cd"+ "ef";
        分析代码可知:首先会生成 ab 对象,再生成 abcd 对象,最后生成 abcdef 对象,从理论上来说,这段代码是低效的。 编译器自动优化了这行代码,编译后的代码,你会发现编译器自动优化了这行代码,如下
String str= "abcdef";

5、大循环使用+,这个跟编译器有关,有的编译器并不会对其进行优化

 

intern方法:

进入源码我们发现,他是个native方法。

/**
     * Returns a canonical representation for the string object.
     * <p>
     * A pool of strings, initially empty, is maintained privately by the
     * class {@code String}.
     * <p>
     * When the intern method is invoked, if the pool already contains a
     * string equal to this {@code String} object as determined by
     * the {@link #equals(Object)} method, then the string from the pool is
     * returned. Otherwise, this {@code String} object is added to the
     * pool and a reference to this {@code String} object is returned.
     * <p>
     * It follows that for any two strings {@code s} and {@code t},
     * {@code s.intern() == t.intern()} is {@code true}
     * if and only if {@code s.equals(t)} is {@code true}.
     * <p>
     * All literal strings and string-valued constant expressions are
     * interned. String literals are defined in section 3.10.5 of the
     * <cite>The Java&trade; Language Specification</cite>.
     *
     * @return  a string that has the same contents as this string, but is
     *          guaranteed to be from a pool of unique strings.
     */
    public native String intern();

注意这段注释 :When the intern method is invoked, if the pool already contains a string equal to this {@code String} object as determined by the {@link #equals(Object)} method, then the string from the pool is returned. Otherwise, this {@code String} object is added to the pool and a reference to this {@code String} object is returned. 

翻译过来就是:当执行invoked方法时,如果常量池中已经存在该字符串,那么将返回这个字符串的引用。反之,在常量池中并不存在这个字符串,则在常量池中新增一个,并将指向这个字符串对象的引用返回。

总结:就是这个这个方法会返回这个字符串在常量池中的引用。如果已经有了这个对象则返回以前的引用,否则新增一个并返回这个引用。

经典例题:下面代码的执行结果

public static void main(String[] args) throws Exception{
        String str1 = "ab";
        String str2 = new String("ab");
        String str3 = new String("ab").intern();

        System.out.println(str1 == str2);
        System.out.println(str2 == str3);
        System.out.println(str1 == str3);
    }

执行结果:

false
false
true

解释:

1、String str1 = "ab"; str1的引用时指向的时字符串常量池的。
2、String str2 = new String("ab"); 任何情况当出现new,那么等号左边的引用一定是指向堆中new出来对象的引用。
3、String str3 = new String("ab").intern(); 只要执行了intern()方法,那么返回的一定是字符串常量池的引用,无论是以前的,还是没有新增的。

 

 

声明:本文为学习享学课堂三期JVM章节课后所做的学习总结,文中借鉴King老师上课笔记和课堂PPT。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值