常量在什么时候被放入常量池?

本文深入探讨了Java中的常量池概念,包括方法区中的运行时常量池和class文件中的常量池。通过实例说明了常量如何在编译期和运行期被放入常量池,并解释了访问类中常量时的行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先来了解一下常量池,常量池分为方法区中的运行时常量池和class文件中的常量池,class文件中的常量池在编译时确定,其中包括符号引用和字面量(文本字符串,被声明为final的变量的),运行时,JVM从中读取数据到方法区的运行时常量池,运行时常量池可以在运行时添加常量,常量可以在运行时或编译时被放入常量池,编译期放入到类文件的常量池中,运行时放入到方法区的运行时常量池中,JDK1.7后运行时常量池位于堆中,访问类中的常量不一定会加载类,如下代码:

class deal
{   static {System.out.println("加载了deal类");}
   static final int x=10000000;
}
public class Try {
    public static void main(String[] args) {
        System.out.println(deal.x);
    } 
}
输出结果为


可见并未加载类deal,这是一种优化,因为访问常量只是为了获取其中的值,我们无法改变它的值,所以不需要加载deal类。当我们的代码访问了类中的常量时,该常量才会在编译时被放入对应类class文件的常量池,否则不会被放入,看看Try.class文件的反编译结果:

Constant pool:
   #1 = Methodref          #7.#16         // java/lang/Object."<init>":()V
   #2 = Fieldref           #17.#18        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Class              #19            // deal
   #4 = Integer            10000000
   #5 = Methodref          #20.#21        // java/io/PrintStream.println:(I)V
   #6 = Class              #22            // Try
   #7 = Class              #23            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               SourceFile
  #15 = Utf8               Try.java
  #16 = NameAndType        #8:#9          // "<init>":()V
  #17 = Class              #24            // java/lang/System
  #18 = NameAndType        #25:#26        // out:Ljava/io/PrintStream;
  #19 = Utf8               deal
  #20 = Class              #27            // java/io/PrintStream
  #21 = NameAndType        #28:#29        // println:(I)V
  #22 = Utf8               Try
  #23 = Utf8               java/lang/Object
  #24 = Utf8               java/lang/System
  #25 = Utf8               out
  #26 = Utf8               Ljava/io/PrintStream;
  #27 = Utf8               java/io/PrintStream
  #28 = Utf8               println
  #29 = Utf8               (I)V
{
  public Try();
    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 5: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #4                  // int 10000000
         5: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
         8: return
      LineNumberTable:
        line 7: 0
        line 8: 8
}
SourceFile: "Try.java"
在常量池索引4处,我们看到了10000000,,接下来我们反编译如下代码:

class deal
{   static {System.out.println("加载了deal类");}
   static final int x=10000000;
}
public class Try {
    public static void main(String[] args) {
    } 
}
反编译Try.class文件后:

Constant pool:
   #1 = Methodref          #3.#12         // java/lang/Object."<init>":()V
   #2 = Class              #13            // Try
   #3 = Class              #14            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               main
   #9 = Utf8               ([Ljava/lang/String;)V
  #10 = Utf8               SourceFile
  #11 = Utf8               Try.java
  #12 = NameAndType        #4:#5          // "<init>":()V
  #13 = Utf8               Try
  #14 = Utf8               java/lang/Object
{
  public Try();
    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 5: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 7: 0
}
SourceFile: "Try.java"
在常量池中并未看到常量10000000

下面讲讲我个人的一些理解:

在编译下列代码(或者说在编译访问常量的代码时)时

System.out.println(deal.x);

编译器做了一个等价处理,编译器从源文件上文得知deal.x是一个常量,所以会将该常量的值10000000放入常量池,同时利用ldc指令进行访问,所以我们实际上是直接访问常量池中的10000000,此时JVM并未分配内存给x,我们可以理解为static final常量在编译期就将值放入到访问了该常量的类文件的常量池中。

下面我们来看看常量如何在运行时加入常量池,由于目前我不知道如何查看运行时常量池,所以无法给出直观的例子。

在运行时常量加入常量池最典型的就是String的intern方法。来看看下面的代码:

public class Try {
    public static void main(String[] args) {
        String m=new String("12")+new String("34");
        String x=m.intern();
    } 
}
intern方法的作用如下:

1、若m的字符串值在常量池中,则返回m的引用。

2、若m的字符串值不在常量池中(在堆内存中),但常量池中含有该字符串值,则返回常量池中该字符串值的引用。

3、若m的字符串值不在常量池中(在堆内存中),且常量池中不含有该字符串值,则在常量池中创建一个与m的字符序列相同的字符串值,然后返回新创建字符串值的引用。

先来看看Try.class文件中的常量池


虽然我们调用了intern,但在Try.class中并未看到字符串"1234",因为在编译时并未运行intern函数,编译器只会收集源文件中标识出并使用的常量或是常量相加后的常量(例如int n=1+2,则3会在编译期被放入常量池),例如上述代码中的“12”和“34”。在代码中调用intern满足情况3,在运行时会将“1234”添加到运行时常量池中。

以上。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值