jvm系列-常量池-简介

本文主要总结一下以下三个知识点:

  1. 类常量池
  2. 运行时常量池
  3. 字符串常量池

一、类常量池

类常量池是.class字节码文件中内容,保存了Java类中大多数信息,如方法信息、变量信息等.

它是.class字节码文件中的概念.

如下,定义一个java类:

package com.study.jvm.mem;

public class UserService {

    private final static Long ID=10L;
    private static String name = "user";
    public String userInfo(){
        String userInfo = "{name:x,age:1}";
        return "userInfo";
    }

}

查看字节码:

javap -v UserService.class 


Classfile /Users/dev/workspace-study/study/jvm/target/classes/com/study/jvm/mem/UserService.class
  Last modified 2022-6-1; size 664 bytes
  MD5 checksum 7e46f52a2426c92ecd39d80a1393fb99
  Compiled from "UserService.java"
public class com.study.jvm.mem.UserService
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #11.#28        // java/lang/Object."<init>":()V
   #2 = String             #29            // {name:x,age:1}
   #3 = String             #23            // userInfo
   #4 = Long               10l
   #6 = Methodref          #30.#31        // java/lang/Long.valueOf:(J)Ljava/lang/Long;
   #7 = Fieldref           #10.#32        // com/study/jvm/mem/UserService.ID:Ljava/lang/Long;
   #8 = String             #33            // user
   #9 = Fieldref           #10.#34        // com/study/jvm/mem/UserService.name:Ljava/lang/String;
  #10 = Class              #35            // com/study/jvm/mem/UserService
  #11 = Class              #36            // java/lang/Object
  #12 = Utf8               ID
  #13 = Utf8               Ljava/lang/Long;
  #14 = Utf8               name
  #15 = Utf8               Ljava/lang/String;
  #16 = Utf8               <init>
  #17 = Utf8               ()V
  #18 = Utf8               Code
  #19 = Utf8               LineNumberTable
  #20 = Utf8               LocalVariableTable
  #21 = Utf8               this
  #22 = Utf8               Lcom/study/jvm/mem/UserService;
  #23 = Utf8               userInfo
  #24 = Utf8               ()Ljava/lang/String;
  #25 = Utf8               <clinit>
  #26 = Utf8               SourceFile
  #27 = Utf8               UserService.java
  #28 = NameAndType        #16:#17        // "<init>":()V
  #29 = Utf8               {name:x,age:1}
  #30 = Class              #37            // java/lang/Long
  #31 = NameAndType        #38:#39        // valueOf:(J)Ljava/lang/Long;
  #32 = NameAndType        #12:#13        // ID:Ljava/lang/Long;
  #33 = Utf8               user
  #34 = NameAndType        #14:#15        // name:Ljava/lang/String;
  #35 = Utf8               com/study/jvm/mem/UserService
  #36 = Utf8               java/lang/Object
  #37 = Utf8               java/lang/Long
  #38 = Utf8               valueOf
  #39 = Utf8               (J)Ljava/lang/Long;
{
  public com.study.jvm.mem.UserService();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/study/jvm/mem/UserService;

  public java.lang.String userInfo();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #2                  // String {name:x,age:1}
         2: astore_1
         3: ldc           #3                  // String userInfo
         5: areturn
      LineNumberTable:
        line 11: 0
        line 12: 3
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Lcom/study/jvm/mem/UserService;
            3       3     1 userInfo   Ljava/lang/String;

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: ldc2_w        #4                  // long 10l
         3: invokestatic  #6                  // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
         6: putstatic     #7                  // Field ID:Ljava/lang/Long;
         9: ldc           #8                  // String user
        11: putstatic     #9                  // Field name:Ljava/lang/String;
        14: return
      LineNumberTable:
        line 5: 0
        line 7: 9
}
SourceFile: "UserService.java"



其中Constant pool,就是.class文件的常量池.

我们可以从中看到我们定义的常量信息和方法信息.

    private final static Long ID=10L;
  字节码表示如下:
   #7 = Fieldref           #10.#32        // com/study/jvm/mem/UserService.ID:Ljava/lang/Long;
   #12 = Utf8               ID
   #32 = NameAndType        #12:#13        // ID:Ljava/lang/Long;

二、运行时常量池

在虚拟机的类加载阶段,jvm会把该.class的字节流所代表的静态存储结构转化为方法区的运行时数据结构.

运行时常量池有以下特点:

  1. 每一个.class文件都会分配一个运行时常量池来存储当前类.class文件中的常量池信息,这些信息主要是编译期生成的各种字面量和符号引用.
  2. 运行时常量池相对于class文件常量池,是动态的.

三、字符串常量池

字符串常量池是专门针对String类型设计的常量池.是当前应用所有线程共享的,每个jvm只有一个.

3.1.为什么要单独对字符串设计一个常量池

首先看下String的定义:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    
}

从源码中可以看出:

  1. String被final修饰,表示无法被继承
  2. 属性value被final修改,表示赋值后无法被修改

所以String具有不可变性.

由于在Java中String变量会被大量使用,如果每一次声明一个String,都为其分配内存空间,存储对应的char[] ,将会导致极大的空间浪费.

所以在jvm中提出了字符串常量池的概念,当初始化一个String变量时,如果该字符串已经在字符串常量池已经存在,就直接返回该字符串的引用.否则,往字符串常量池添加该字符串,并返回其引用.

其引用关系如下:

在这里插入图片描述

3.2.从几个例子来理解字符串常量池

3.2.1.String.intern()

package com.study.jvm.mem;

public class StringService {

    public static void main(String[] args) {
        String str1 = new String("abc");
        String str2 = "abc";
        String str3 = str1.intern();
        System.out.println("str1 == str2:"+(str1==str2));
        System.out.println("str2 == str3:"+(str3==str2));
        System.out.println("str1 == str3:"+(str1==str2));
    }
}

str1 == str2:false
str2 == str3:true
str1 == str3:false

str1:指向地址为堆中为string对象分配的内存地址

str2: 指向字符串常量池 abc 的地址

intern操作的含义:

  1. 将当前字符串添加到字符串常量池,并返回该字符串在字符串常量池的内存地址
    1. 如果字符串常量池已经存在该字符串,则直接返回该字符串地址
String str1 = new String("abc");

str1在内存中的string对象,在初始化完成后,对象的实例数据部分会存储"abc"这个内容.

但是经过intern操作,会将str1堆内对象的数据引用指向字符串常量池的"abc",如3.1内的图.

3.2.2.String+String

package com.study.jvm.mem;

public class StringService {

    public static void main(String[] args) {
        String str1 = new String("abc");
        String str2 = "abc";
        String str3 = new String("a")+new String("bc");
        System.out.println("str1 == str2:"+(str1==str2));
        System.out.println("str2 == str3:"+(str3==str2));
        System.out.println("str1 == str3:"+(str1==str2));
        System.out.println("str1.intern == str3.intern:"+(str1.intern()==str2.intern()));
        System.out.println("str2.intern == str3.intern:"+(str2.intern()==str2.intern()));

    }
}

看下这段代码对应的字节码:

Code:
      stack=4, locals=4, args_size=1
         0: new           #2                  // class java/lang/String
         3: dup
         4: ldc           #3                  // String abc
         6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
         9: astore_1
        10: ldc           #3                  // String abc
        12: astore_2
        13: new           #5                  // class java/lang/StringBuilder
        16: dup
        17: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        20: new           #2                  // class java/lang/String
        23: dup
        24: ldc           #7                  // String a
        26: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        29: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        32: new           #2                  // class java/lang/String
        35: dup
        36: ldc           #9                  // String bc
        38: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        41: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        44: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        47: astore_3

我们可以看出

        String str3 = new String("a")+new String("bc");

这行代码一共做了以下操作:

  1. new StringBuilder
  2. new String(“a”)
  3. 在字符串常量池 添加 a
  4. new String(“bc”)
  5. 在字符串添加 bc
  6. StringBuilder.toString() 操作又new String(“abc”),但是未往字符串常量池添加.

所以str1,str2,str3对应的内存分布如下:

在这里插入图片描述

堆上的字符串对象a和字符串对象bc,会在最近的一次垃圾回收时被回收,因为根本没有不可达.

3.2.3.总结

  1. new String 返回的时堆上的地址,但是不会把string自动添加到字符串常量池
  2. String a = “abc”,会自动把abc添加到字符串常量池,并返回字符串在字符串常量池的内存地址
  3. String.intern会把当前堆上的字符串添加到字符串常量池,并把堆上该字符串引用指向到字符串常量池字符串地址,
  4. 在程序中定义字符串推荐 String a= “abc”,或String a = new String(“abc”).intern,提高字符串利用率.

3.3.其他类型的常量池

在java中,除了存在字符串常量池,其他封装类也有对应的常量池,只不过字符串常量池是jvm级别的,而其他封装类常量池是在各自的类里面实现.

这些常量池范围如下:

  1. Byte、Short、Integer、Long:[-128,127]
  2. Character:[0,127]
  3. Boolean: [True,False]

以Integer为例:

public final class Integer extends Number implements Comparable<Integer> {


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

 private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

}

常量池生效是在调用valueOf方法时.直接new的化,还是失效的.

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿老徐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值