JVM(七)JVM常量池详解

一、Class常量池解析

  定义:Class常量池可以理解为是Class文件中的资源仓库

  内容:Class文件中除了包含类的版本、字段、方法、接口等描述信息外, 还有一项信息就是常量池,用于存放编译期生成的各种字面量和符号引用

  我们可以通过一个命令来查看我们字节码文件的内容:

字面量

  定义:字面量就是指由字母、数字等构成的字符串或者数值常量

  PS:字面量只可以右值出现【等号右边的值】如:int a = 1 这里的a为左值,1为右值。在这个例子中1就是字面量。

private int compute() {
    int a = 1;//符号引用:a 字面量:1
    int b = 2;//符号引用:b 字面量:2
    String c = "有梦想的肥宅";//符号引用:c 字面量:有梦想的肥宅
    return a + b;
}

符号引用

  符号引用是编译原理中的概念,是相对于直接引用来说的。主要包括了以下三类常量:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

  符号引用只有到运行时被加载到内存后,这些符号才有对应的内存地址信息,这些常量池一旦被装入内存就变成运行时常量池,也就引出了下面动态链接的概念。

  动态链接:对应的符号引用在程序加载或运行时会被转变为被加载到内存区域的代码的直接引用。

  例:compute()这个符号引用在运行时就会被转变为compute()方法具体代码在内存中的地址,主要通过对象头里的类型指针去转换直接引用。

二、字符串常量池解析

字符串常量池的设计思想

  字符串的分配和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能。JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化:

  • 为字符串开辟一个字符串常量池,类似于缓存区
  • 创建字符串常量时,首先查询字符串常量池是否存在该字符串
  • 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中

三种字符串操作(Jdk1.7 及以上版本)

直接赋值

String s = "有梦想的肥宅"; // s :指向常量池中的引用

  PS:这种方式创建的字符串对象,只会在常量池中

  创建步骤:

  JVM会先去常量池中通过 equals(key) 方法,判断是否有相同的对象:

  • ,则直接返回该对象在常量池中的引用
  • 没有,则会在常量池中创建一个新对象,再返回引用

new String()方法创建

String s1 = new String("有梦想的肥宅"); // s1指向内存中的对象引用

  PS:这种方式会保证字符串常量池和堆中都有这个对象,没有就创建,最后返回堆内存中的对象引用

  创建步骤:

  因为有"有梦想的肥宅"这个字面量,所以会先检查字符串常量池中是否存在此字符串:

  • 不存在,先在字符串常量池里创建一个字符串对象再去内存中创建一个字符串对象"有梦想的肥宅"
  • 存在,就直接去堆内存中创建一个字符串对象"zhuge", 最后,将内存中的引用返回

intern()方法

String s1 = new String("有梦想的肥宅"); 
String s2 = s1.intern(); //【intern:是一个native方法】
System.out.println(s1 == s2); //false

  PS:这种方式创建的字符串对象,可能在常量池中存在,也可能不存在【常量池保存一个内存地址的引用,指向堆中对应的字符串对象】

  创建步骤:

  还是会去常量池找看有没有"有梦想的肥宅"这个字符串:

  • 存在,则返回池中的字符串
  • 不存在,将intern返回的引用指向当前字符串 s1  

  PS:jdk1.6版本需要将 s1 复制到字符串常量池里

三、八种基本类型的包装类和对象池

  java中基本类型的包装类的大部分都实现了常量池技术(严格来说对象在堆上应该叫对象池),这些类是 Byte、Short、Integer、Long、Character、Boolean,另外两种浮点数类型的包装类则没有实现。

  PS:Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用对象池,因为一般这种比较小的数用到的概率相对较大。

public class Test {
    public static void main(String[] args) {
        //1、5种整形的包装类Byte,Short,Integer,Long,Character的对象,在值小于127时可以使用对象池
        Integer i1 = 127; //PS:这种调用底层实际是执行的Integer.valueOf(127),里面用到了IntegerCache对象池
        Integer i2 = 127;
        System.out.println(i1 == i2);//输出true

        //2、当值大于127时,不会从对象池中取对象
        Integer i3 = 128;
        Integer i4 = 128;
        System.out.println(i3 == i4);//输出false

        //3、用new关键词新生成对象不会使用对象池
        Integer i5 = new Integer(127);
        Integer i6 = new Integer(127);
        System.out.println(i5 == i6);//输出false

        //4、Boolean类也实现了对象池技术
        Boolean bool1 = true;
        Boolean bool2 = true;
        System.out.println(bool1 == bool2);//输出true

        //5、浮点类型的包装类没有实现对象池技术
        Double d1 = 1.0D;
        Double d2 = 1.0D;
        System.out.println(d1 == d2);//输出false
    }
}
原文地址:https://www.cnblogs.com/riches/p/14707509.html