java 为什么需要常量池

news/2024/6/26 9:32:28

java中讲的常量池,通常指的是运行时常量池,它是方法区的一部分,一个jvm实例只有一个运行常量池,各线程间共享该运行常量池。

java内存模型中将内存分为堆和栈,其中堆为线程间共享的内存数据区域,栈为线程间私有的内存区域。堆又包括方法区以及非方法区部分,栈包括本地方法栈、虚拟机栈等,如下图所示:

图片描述

为什么需要常量池

jvm 在栈帧(frame) 中进行操作数和方法的动态链接(link),为了便于链接,jvm 使用常量池来保存跟踪当前类中引用的其他类及其成员变量和成员方法。

每个栈帧(frame)都包含一个运行常量池的引用,这个引用指向当前栈帧需要执行的方法,jvm使用这个引用来进行动态链接。

在 c/c++ 中,编译器将多个编译期编译的文件链接成一个可执行文件或者dll文件,在链接阶段,符号引用被解析为实际地址。java 中这种链接是在程序运行时动态进行的。

图片描述

常量池探秘

每个 java 文件编译为 class 文件后,都将产生当前类独有的常量池,我们称之为静态常量池。class 文件中的常量池包含两部分:字面值(literal)和符号引用(Symbolic Reference)。其中字面值可以理解为 java 中定义的字符串常量、final 常量等;符号引用指的是一些字符串,这些字符串表示当前类引用的外部类、方法、变量等的引用地址的抽象表示形式,在类被jvm装载并第一次使用这些符号引用时,这些符号引用将会解析为直接引用。符号常量包含:

  • 类和接口的全限定名

  • 字段的名称和描述符

  • 方法的名称和描述符

jvm在进行类装载时,将class文件中常量池部分的常量加载到方法区中,此时方法区中的保存常量的逻辑区域称之为运行时常量区。

使用javap -verbose 命令可以查看class字节码的详细信息,其中包含了编译期确定的静态常量池。

public class StringTest {public static void main(String[] args){String s = new String("abc");String s2 = s.intern();System.out.println(s2 == s);String s3 = (s + s2);System.out.println(s3 == s3.intern());}
}

上述代码javap -verbose后得到(只拿出常量池部分):

major version: 52
Constant pool:#1 = Methodref          #13.#26        // java/lang/Object."<init>":()V#2 = Class              #27            // java/lang/String#3 = String             #28            // abc#4 = Methodref          #2.#29         // java/lang/String."<init>":(Ljava/lang/String;)V#5 = Methodref          #2.#30         // java/lang/String.intern:()Ljava/lang/String;#6 = Fieldref           #31.#32        // java/lang/System.out:Ljava/io/PrintStream;#7 = Methodref          #33.#34        // java/io/PrintStream.println:(Z)V#8 = Class              #35            // java/lang/StringBuilder#9 = Methodref          #8.#26         // java/lang/StringBuilder."<init>":()V#10 = Methodref          #8.#36         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#11 = Methodref          #8.#37         // java/lang/StringBuilder.toString:()Ljava/lang/String;#12 = Class              #38            // StringTest#13 = Class              #39            // java/lang/Object#14 = Utf8               <init>#15 = Utf8               ()V#16 = Utf8               Code#17 = Utf8               LineNumberTable#18 = Utf8               main#19 = Utf8               ([Ljava/lang/String;)V#20 = Utf8               StackMapTable#21 = Class              #40            // "[Ljava/lang/String;"#22 = Class              #27            // java/lang/String#23 = Class              #41            // java/io/PrintStream#24 = Utf8               SourceFile#25 = Utf8               StringTest.java#26 = NameAndType        #14:#15        // "<init>":()V#27 = Utf8               java/lang/String#28 = Utf8               abc#29 = NameAndType        #14:#42        // "<init>":(Ljava/lang/String;)V#30 = NameAndType        #43:#44        // intern:()Ljava/lang/String;#31 = Class              #45            // java/lang/System#32 = NameAndType        #46:#47        // out:Ljava/io/PrintStream;#33 = Class              #41            // java/io/PrintStream#34 = NameAndType        #48:#49        // println:(Z)V#35 = Utf8               java/lang/StringBuilder#36 = NameAndType        #50:#51        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#37 = NameAndType        #52:#44        // toString:()Ljava/lang/String;#38 = Utf8               StringTest#39 = Utf8               java/lang/Object#40 = Utf8               [Ljava/lang/String;#41 = Utf8               java/io/PrintStream#42 = Utf8               (Ljava/lang/String;)V#43 = Utf8               intern#44 = Utf8               ()Ljava/lang/String;#45 = Utf8               java/lang/System#46 = Utf8               out#47 = Utf8               Ljava/io/PrintStream;#48 = Utf8               println#49 = Utf8               (Z)V#50 = Utf8               append#51 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;#52 = Utf8               toString

我们可以看到,常量池共包含52个常量。#1 是一个类中方法的符号引用,它由 #13#26 两个utf8编码的字符串构成;#3 是程序中定义的 String 类型的字面值 "abc",它包含指向一个utf8编码字符串 "abc" 的索引 #28

方法的调用、成员变量的访问最终都是通过运行时常量池来查找具体地址的。

String 常量池

运行时常量池有一种 String 类型的常量,即通常我们所说的字符串字面值,所有的字符串字面值组成一个 String 常量表。String常量表并不是一成不变的,程序运行时可以动态添加字符串常量,使用String的intern()可以动态的添加String常量。但

jvm 确保两个在值上完全相等的字符串字面值(即其中包含的字符序列是相同的,使用equals()来判断)指向同一个 String 实例。

如:

String s1 = "abc";String s2 = "abc";System.out.println(s1 == s2); // true

上述代码中的字符串 s1 和 s2 将指向同一个 String 实例。实际上通过查看class文件,我们可以看到,在编译后,静态常量池中已经包含了一个 String 类型的字面值 "abc",程序运行时只是从常量池中获取这个String字面值的引用地址,并赋值给变量 s1 和变量 s2。

Constant pool:#1 = Methodref          #6.#19         // java/lang/Object."<init>":()V#2 = String             #20            // abc······#20 = Utf8               abcpublic static void main(java.lang.String[]);······Code:stack=3, locals=3, args_size=10: ldc           #2                  // String abc2: astore_13: ldc           #2                  // String abc

其中,ldc 表示将一个常量加载到操作数栈。

String 的 intern() 是一个native方法,返回的是一个String对象的标准表示。当调用该方法时,如果运行时常量池中已经存在与之相等(equal())的字符串,则直接返回常量池中的字符串引用,否则将此字符串添加到池中,并返回。

String s1 = "abc";String s2 = new String("abc");System.out.println(s1 == s2);           //返回 falseSystem.out.println(s1.equals(s2));      //返回 trueSystem.out.println(s1 == s2.intern());  //返回 true

上述代码中,虽然 s1 和 s2 中的值是相同的,但是他们指向的并不是同一个对象,但 s2 的标准化表示和s1是同一个 String 对象,都是编译期确定的常量池中的 "abc"。


http://lihuaxi.xjx100.cn/news/285814.html

相关文章

2019全球AI 100强,中国占独角兽半壁江山,但忧患暗存

整理 | apddd出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;日前&#xff0c;创投研究机构CB Insights发布了年度人工智能企业百强榜单——由100个最具前途的AI公司组成&#xff0c;它们从3000多个候选者中脱颖而出&#xff0c;其业务涵盖人工智能硬件、数据基础设施…

POJ2492 A Bug s Life 题解

题解&#xff1a;并查集的简单应用。 给你n个元素之间的关系&#xff0c;告诉你他们性别是不同的&#xff0c;这种问题属于带权并查集&#xff0c;比裸的并查集稍稍多了一步。 首先&#xff0c;你需要开辟一个数组val来记录x与它父节点之间的关系 比如说&#xff1a; val[x]0代…

使用C++实现Socket编程图片打包传输(修改)

使用&#xff1a; &#xff08;1&#xff09;首先运行服务端&#xff0c;待服务端运行起来&#xff1b; &#xff08;2&#xff09;最后运行客户端&#xff0c;输入要传输文件到哪个目标机器的IP地址&#xff1b; &#xff08;3&#xff09;输入传输文件的路径及文件&#xff0…

中国爬虫违法违规案例汇总!

最近在 GitHub 发现了一个爬虫库&#xff0c;这个库整理了所有中国大陆爬虫开发者涉诉与违规相关的新闻、资料与法律法规。GitHub 地址&#xff1a;https://github.com/HiddenStrawberry/Crawler_Illegal_Cases_In_China该库初衷是为了帮助在中国大陆工作的爬虫行业从业者了解我…

编程入门,这763位老程序员有话讲!

作者 | lafur Waage译者 | 弯月来源 | CSDN&#xff08;ID&#xff1a;CSDNNews&#xff09;概述瑞典马尔默有一所名为 The Game Assembly 的学校。这所学校专注于教学生游戏制作。从编程到艺术和设计&#xff0c;所有内容都教。这是一个为期三年的计划&#xff0c;最后一年会去…

Linux运维启蒙

2020年初的一场疫情&#xff0c;影响着整个中国甚至是整个世界&#xff0c;就业市场也面临着洗礼&#xff0c;千家企业破产&#xff0c;失业人数不计其数。不过凡事皆有俩面性&#xff0c;危和机总是并存的&#xff0c;度过了危机便会迎来机遇&#xff0c;疫情后&#xff0c;医…

[0x17基本数据结构-二叉堆]-Supermarket

题意&#xff1a; 超市里有N件商品&#xff0c;每件商品都有利润pi和过期时间di,每天只能卖一件商品&#xff0c;过期商品不能再卖。求合理安排每天卖的商品的情况下&#xff0c;可以得到的最大收益是多少。 输入格式&#xff1a; 输入包含多组测试用例。 每组测试用例&#xf…

使用C++实现Socket编程传输协议文件(包括大文件)

使用&#xff1a; &#xff08;1&#xff09;首先运行服务端&#xff0c;待服务端运行起来&#xff1b; &#xff08;2&#xff09;最后运行客户端&#xff0c;输入要传输文件到哪个目标机器的IP地址&#xff1b; &#xff08;3&#xff09;输入传输文件的路径及文件&#xff0…